Compare commits

..

56 Commits

Author SHA1 Message Date
cf9f04ecdc Float fixes, sensible settings 2025-09-14 21:27:14 -04:00
ce3f0b3415 SEMICOLON MOMENT 2025-09-14 21:03:11 -04:00
e0732ee6b9 Fixes for exit confirmation when saved 2025-09-14 21:00:54 -04:00
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
9ad464a74a Onionskin, input rebinding, alt icons, settings refactor 2025-09-08 20:18:28 -04:00
4d098f8f1c Fixes to generate preview 2025-09-05 20:07:49 -04:00
a992661c6a Removed GLEW dependency (using GLAD instead), axes now use shader, should now work better 2025-09-05 16:37:45 -04:00
fa6109cb2e Updated settings, added reset to default settings option, tried to fix -1 map bug 2025-09-01 22:00:12 -04:00
d50fded04d Update cmake 2025-09-01 18:09:26 -04:00
d92dbc5b6d This is again a bandaid solution to this map with -1 problem, but it'll work for the normies 2025-08-28 17:00:21 -04:00
2c62f5701c Hotfix for adding frames, spritesheets, etc. 2025-08-28 08:37:47 -07:00
8f3c666922 Need to save anm2 first before doing anything, should eliminate a few problems 2025-08-28 10:27:47 -04:00
Shweet
de034b9b29 Update README.md 2025-08-27 22:43:01 -04:00
0ddc616e02 Update default settings 2025-08-27 22:22:08 -07:00
0f06837965 Update README.md 2025-08-27 18:29:03 -07:00
e2f1afbefb Update README.md 2025-08-27 18:28:38 -07:00
b667710c99 Windows builds updates; prepare for release 2025-08-27 18:23:12 -07:00
3546f27655 Moved spritesheet texture handling to anm2 instead of resources; added undoing for spritesheet texture changes; refactoring 2025-08-27 00:28:41 -04:00
c9056ce707 I have no idea why I used SDL_CreateWindowAndRenderer, that's only for applications using the SDL_Renderer, not opengl 2025-08-15 23:47:39 -04:00
ff69218d50 exit confirmation changes 2025-08-15 18:20:13 -04:00
afbaf2bc65 Additional logging + FFMpeg changes; ffmpeg output will be in the log now 2025-08-15 18:03:45 -04:00
8388fd5d99 logging 2025-08-15 17:17:11 -04:00
f75369f670 dragging frames with ctrl 2025-08-15 16:18:00 -04:00
5ee3ec4351 Changing how string format for imgui float widgets work, suppressing stbi warnings 2025-08-15 14:27:44 -04:00
67d36df971 Canvas size fix 2025-08-15 13:52:46 -04:00
28c0de1848 ffmpeg dialog file filter fix for windows 2025-08-15 13:00:55 -04:00
903c77019f Exit confirmation change 2025-08-15 12:49:45 -04:00
f7bebe6558 SDL_Delay 2025-08-15 12:36:39 -04:00
76271dc6b8 SDL_Delay 2025-08-15 12:36:14 -04:00
e721f89972 Semicolon moment 2025-08-15 12:30:18 -04:00
a665023626 Refactor, Vsync, FFmpeg fixes 2025-08-15 12:18:57 -04:00
Shweet
e8094a19c4 Merge pull request #3 from im-tem/master
Windows bringup v2 feat. vsync
2025-08-15 10:17:53 -04:00
im-tem
5b4b0b2be5 enable vsync to prevent excess resource usage 2025-08-15 12:41:03 +03:00
im-tem
b3f99616c9 cmakelists: reintroduce /stack for win32 2025-08-15 12:28:17 +03:00
im-tem
73ba6af125 misc: fixes to make it build 2025-08-15 12:13:26 +03:00
im-tem
cbf3a5949a cmakelists: adjust windows icon path 2025-08-15 12:07:29 +03:00
0b8647fe4d quick undo change 2025-08-14 22:23:17 -04:00
48 changed files with 29045 additions and 2481 deletions

7
.gitignore vendored
View File

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

View File

@@ -1,4 +1,5 @@
cmake_minimum_required(VERSION 3.15) cmake_minimum_required(VERSION 3.15)
if(WIN32 AND DEFINED ENV{VCPKG_ROOT} AND NOT DEFINED CMAKE_TOOLCHAIN_FILE) if(WIN32 AND DEFINED ENV{VCPKG_ROOT} AND NOT DEFINED CMAKE_TOOLCHAIN_FILE)
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
CACHE STRING "Vcpkg toolchain file") CACHE STRING "Vcpkg toolchain file")
@@ -7,52 +8,68 @@ endif()
project(anm2ed CXX) project(anm2ed CXX)
find_package(SDL3 REQUIRED) find_package(SDL3 REQUIRED)
find_package(GLEW REQUIRED)
find_package(OpenGL REQUIRED) find_package(OpenGL REQUIRED)
# Gather project sources set(GLAD_SRC
file(GLOB SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/include/glad/glad.cpp
"include/imgui/imgui.cpp"
"include/imgui/imgui_draw.cpp"
"include/imgui/imgui_tables.cpp"
"include/imgui/imgui_widgets.cpp"
"include/imgui/backends/imgui_impl_sdl3.cpp"
"include/imgui/backends/imgui_impl_opengl3.cpp"
"include/tinyxml2/tinyxml2.cpp"
"${PROJECT_SOURCE_DIR}/src/*.cpp"
"${PROJECT_SOURCE_DIR}/src/*.h"
) )
if (WIN32) set(IMGUI_SRC
enable_language("RC") ${CMAKE_CURRENT_SOURCE_DIR}/include/imgui/imgui.cpp
set (WIN32_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/assets/win_icon.rc) ${CMAKE_CURRENT_SOURCE_DIR}/include/imgui/imgui_draw.cpp
endif() ${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} ${WIN32_RESOURCES}) 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)
target_sources(${PROJECT_NAME} PRIVATE assets/Icon.rc)
set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE TRUE)
endif()
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23) target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_23)
target_include_directories(${PROJECT_NAME} PRIVATE include 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 (NOT MSVC) if(MSVC)
set(CMAKE_CXX_FLAGS "-O2 -std=c++23 -Wall -Wextra -pedantic -fmax-errors=1") target_compile_options(${PROJECT_NAME} PRIVATE /std:c++latest /EHsc)
else() target_link_options(${PROJECT_NAME} PRIVATE /STACK:0xffffff)
set(CMAKE_CXX_FLAGS "/std:c++latest /EHsc")
endif()
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DDEBUG -g")
else() else()
target_compile_options(${PROJECT_NAME} PRIVATE -O2 -std=c++23 -Wall -Wextra -pedantic -fmax-errors=1)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(${PROJECT_NAME} PRIVATE -DDEBUG -g)
else()
set(CMAKE_BUILD_TYPE "Release") set(CMAKE_BUILD_TYPE "Release")
endif() endif()
if(NOT MSVC)
target_link_libraries(${PROJECT_NAME} PRIVATE m) target_link_libraries(${PROJECT_NAME} PRIVATE m)
endif() endif()
target_link_libraries(${PROJECT_NAME} PRIVATE OpenGL::GL GLEW::GLEW SDL3::SDL3) target_link_libraries(${PROJECT_NAME} PRIVATE OpenGL::GL SDL3::SDL3)
message("System: ${CMAKE_SYSTEM_NAME}") message("System: ${CMAKE_SYSTEM_NAME}")
message("Project: ${PROJECT_NAME}") message("Project: ${PROJECT_NAME}")
message("Build: ${CMAKE_BUILD_TYPE}") message("Build: ${CMAKE_BUILD_TYPE}")
message("Flags: ${CMAKE_CXX_FLAGS}")

26
CMakeSettings.json Normal file
View File

@@ -0,0 +1,26 @@
{
"configurations": [
{
"name": "x64-Debug",
"generator": "Ninja",
"configurationType": "Debug",
"inheritEnvironments": [ "msvc_x64_x64" ],
"buildRoot": "${projectDir}\\build\\${name}",
"installRoot": "${projectDir}\\install\\${name}",
"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

@@ -1,32 +1,46 @@
# Anm2Ed # Anm2Ed
![Preview](https://shweetz.net/files/projects/anm2ed/preview.png) ![Preview](https://shweetz.net/files/projects/anm2ed/screenshot.png)
A reimplementation of *The Binding of Isaac: Rebirth*'s proprietary animation editor. Manipulates the XML-based ".anm2" format, used for in-game tweened animations. A reimplementation of *The Binding of Isaac: Rebirth*'s proprietary animation editor. Manipulates the XML-based ".anm2" format, used for in-game tweened animations.
## Features ## Features
- Extended version of the original proprietary Nicalis animation editor - 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 - New features
- Can output .webm or *.png sequence - Can output .webm, .mp4 or *.png sequence (wih FFmpeg)
- Cutting, copying and pasting - Cutting, copying and pasting
- Additional wizard options - Additional wizard options
- Robust snapshot (undo/redo) system - 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) - 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 ## Dependencies
Download these from your package manager: Download these from your package manager:
- SDL3 - SDL3
- GLEW
Note, to render animations, you'll need to download [FFmpeg](https://ffmpeg.org/download.html) and specify its install path in the program. ## Build
## Build (Linux) ### Windows
Visual Studio is recommended; make sure your installation has "Desktop development with C++" and ".NET desktop development" workloads.
Install and configure [vcpkg](https://vcpkg.io/en/).
Build should be straightforward from there.
### Linux
After cloning and enter the repository's directory, make sure to initialize the submodules: After cloning and enter the repository's directory, make sure to initialize the submodules:
```git submodules update --init``` ```git submodule update --init```
Then: Then:
@@ -36,3 +50,6 @@ cd build
cmake .. cmake ..
make 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."

311
include/KHR/khrplatform.h Normal file
View File

@@ -0,0 +1,311 @@
#ifndef __khrplatform_h_
#define __khrplatform_h_
/*
** Copyright (c) 2008-2018 The Khronos Group Inc.
**
** Permission is hereby granted, free of charge, to any person obtaining a
** copy of this software and/or associated documentation files (the
** "Materials"), to deal in the Materials without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Materials, and to
** permit persons to whom the Materials are furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Materials.
**
** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
/* Khronos platform-specific types and definitions.
*
* The master copy of khrplatform.h is maintained in the Khronos EGL
* Registry repository at https://github.com/KhronosGroup/EGL-Registry
* The last semantic modification to khrplatform.h was at commit ID:
* 67a3e0864c2d75ea5287b9f3d2eb74a745936692
*
* Adopters may modify this file to suit their platform. Adopters are
* encouraged to submit platform specific modifications to the Khronos
* group so that they can be included in future versions of this file.
* Please submit changes by filing pull requests or issues on
* the EGL Registry repository linked above.
*
*
* See the Implementer's Guidelines for information about where this file
* should be located on your system and for more details of its use:
* http://www.khronos.org/registry/implementers_guide.pdf
*
* This file should be included as
* #include <KHR/khrplatform.h>
* by Khronos client API header files that use its types and defines.
*
* The types in khrplatform.h should only be used to define API-specific types.
*
* Types defined in khrplatform.h:
* khronos_int8_t signed 8 bit
* khronos_uint8_t unsigned 8 bit
* khronos_int16_t signed 16 bit
* khronos_uint16_t unsigned 16 bit
* khronos_int32_t signed 32 bit
* khronos_uint32_t unsigned 32 bit
* khronos_int64_t signed 64 bit
* khronos_uint64_t unsigned 64 bit
* khronos_intptr_t signed same number of bits as a pointer
* khronos_uintptr_t unsigned same number of bits as a pointer
* khronos_ssize_t signed size
* khronos_usize_t unsigned size
* khronos_float_t signed 32 bit floating point
* khronos_time_ns_t unsigned 64 bit time in nanoseconds
* khronos_utime_nanoseconds_t unsigned time interval or absolute time in
* nanoseconds
* khronos_stime_nanoseconds_t signed time interval in nanoseconds
* khronos_boolean_enum_t enumerated boolean type. This should
* only be used as a base type when a client API's boolean type is
* an enum. Client APIs which use an integer or other type for
* booleans cannot use this as the base type for their boolean.
*
* Tokens defined in khrplatform.h:
*
* KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values.
*
* KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0.
* KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0.
*
* Calling convention macros defined in this file:
* KHRONOS_APICALL
* KHRONOS_APIENTRY
* KHRONOS_APIATTRIBUTES
*
* These may be used in function prototypes as:
*
* KHRONOS_APICALL void KHRONOS_APIENTRY funcname(
* int arg1,
* int arg2) KHRONOS_APIATTRIBUTES;
*/
#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC)
# define KHRONOS_STATIC 1
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APICALL
*-------------------------------------------------------------------------
* This precedes the return type of the function in the function prototype.
*/
#if defined(KHRONOS_STATIC)
/* If the preprocessor constant KHRONOS_STATIC is defined, make the
* header compatible with static linking. */
# define KHRONOS_APICALL
#elif defined(_WIN32)
# define KHRONOS_APICALL __declspec(dllimport)
#elif defined (__SYMBIAN32__)
# define KHRONOS_APICALL IMPORT_C
#elif defined(__ANDROID__)
# define KHRONOS_APICALL __attribute__((visibility("default")))
#else
# define KHRONOS_APICALL
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIENTRY
*-------------------------------------------------------------------------
* This follows the return type of the function and precedes the function
* name in the function prototype.
*/
#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__)
/* Win32 but not WinCE */
# define KHRONOS_APIENTRY __stdcall
#else
# define KHRONOS_APIENTRY
#endif
/*-------------------------------------------------------------------------
* Definition of KHRONOS_APIATTRIBUTES
*-------------------------------------------------------------------------
* This follows the closing parenthesis of the function prototype arguments.
*/
#if defined (__ARMCC_2__)
#define KHRONOS_APIATTRIBUTES __softfp
#else
#define KHRONOS_APIATTRIBUTES
#endif
/*-------------------------------------------------------------------------
* basic type definitions
*-----------------------------------------------------------------------*/
#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__)
/*
* Using <stdint.h>
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
/*
* To support platform where unsigned long cannot be used interchangeably with
* inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t.
* Ideally, we could just use (u)intptr_t everywhere, but this could result in
* ABI breakage if khronos_uintptr_t is changed from unsigned long to
* unsigned long long or similar (this results in different C++ name mangling).
* To avoid changes for existing platforms, we restrict usage of intptr_t to
* platforms where the size of a pointer is larger than the size of long.
*/
#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__)
#if __SIZEOF_POINTER__ > __SIZEOF_LONG__
#define KHRONOS_USE_INTPTR_T
#endif
#endif
#elif defined(__VMS ) || defined(__sgi)
/*
* Using <inttypes.h>
*/
#include <inttypes.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(_WIN32) && !defined(__SCITECH_SNAP__)
/*
* Win32
*/
typedef __int32 khronos_int32_t;
typedef unsigned __int32 khronos_uint32_t;
typedef __int64 khronos_int64_t;
typedef unsigned __int64 khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif defined(__sun__) || defined(__digital__)
/*
* Sun or Digital
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#if defined(__arch64__) || defined(_LP64)
typedef long int khronos_int64_t;
typedef unsigned long int khronos_uint64_t;
#else
typedef long long int khronos_int64_t;
typedef unsigned long long int khronos_uint64_t;
#endif /* __arch64__ */
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#elif 0
/*
* Hypothetical platform with no float or int64 support
*/
typedef int khronos_int32_t;
typedef unsigned int khronos_uint32_t;
#define KHRONOS_SUPPORT_INT64 0
#define KHRONOS_SUPPORT_FLOAT 0
#else
/*
* Generic fallback
*/
#include <stdint.h>
typedef int32_t khronos_int32_t;
typedef uint32_t khronos_uint32_t;
typedef int64_t khronos_int64_t;
typedef uint64_t khronos_uint64_t;
#define KHRONOS_SUPPORT_INT64 1
#define KHRONOS_SUPPORT_FLOAT 1
#endif
/*
* Types that are (so far) the same on all platforms
*/
typedef signed char khronos_int8_t;
typedef unsigned char khronos_uint8_t;
typedef signed short int khronos_int16_t;
typedef unsigned short int khronos_uint16_t;
/*
* Types that differ between LLP64 and LP64 architectures - in LLP64,
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
* to be the only LLP64 architecture in current use.
*/
#ifdef KHRONOS_USE_INTPTR_T
typedef intptr_t khronos_intptr_t;
typedef uintptr_t khronos_uintptr_t;
#elif defined(_WIN64)
typedef signed long long int khronos_intptr_t;
typedef unsigned long long int khronos_uintptr_t;
#else
typedef signed long int khronos_intptr_t;
typedef unsigned long int khronos_uintptr_t;
#endif
#if defined(_WIN64)
typedef signed long long int khronos_ssize_t;
typedef unsigned long long int khronos_usize_t;
#else
typedef signed long int khronos_ssize_t;
typedef unsigned long int khronos_usize_t;
#endif
#if KHRONOS_SUPPORT_FLOAT
/*
* Float type
*/
typedef float khronos_float_t;
#endif
#if KHRONOS_SUPPORT_INT64
/* Time types
*
* These types can be used to represent a time interval in nanoseconds or
* an absolute Unadjusted System Time. Unadjusted System Time is the number
* of nanoseconds since some arbitrary system event (e.g. since the last
* time the system booted). The Unadjusted System Time is an unsigned
* 64 bit value that wraps back to 0 every 584 years. Time intervals
* may be either signed or unsigned.
*/
typedef khronos_uint64_t khronos_utime_nanoseconds_t;
typedef khronos_int64_t khronos_stime_nanoseconds_t;
#endif
/*
* Dummy value used to pad enum types to 32 bits.
*/
#ifndef KHRONOS_MAX_ENUM
#define KHRONOS_MAX_ENUM 0x7FFFFFFF
#endif
/*
* Enumerated boolean type
*
* Values other than zero should be considered to be true. Therefore
* comparisons should not be made against KHRONOS_TRUE.
*/
typedef enum {
KHRONOS_FALSE = 0,
KHRONOS_TRUE = 1,
KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM
} khronos_boolean_enum_t;
#endif /* __khrplatform_h_ */

9045
include/glad/glad.cpp Normal file

File diff suppressed because one or more lines are too long

15776
include/glad/glad.h Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,7 @@
#pragma once #pragma once
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <GL/glew.h> #include <glad/glad.h>
#include <GL/gl.h> #include <GL/gl.h>
#include <glm/glm/glm.hpp> #include <glm/glm/glm.hpp>
#include <glm/glm/gtc/type_ptr.hpp> #include <glm/glm/gtc/type_ptr.hpp>
@@ -10,6 +10,7 @@
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <climits>
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <filesystem> #include <filesystem>
@@ -23,10 +24,9 @@
#include <ranges> #include <ranges>
#include <string> #include <string>
#include <unordered_set> #include <unordered_set>
#include <variant>
#include <vector> #include <vector>
typedef uint8_t u8; typedef uint8_t u8;
typedef uint16_t u16; typedef uint16_t u16;
typedef uint32_t u32; typedef uint32_t u32;
@@ -45,52 +45,34 @@ typedef double f64;
using namespace glm; using namespace glm;
#define PREFERENCES_DIRECTORY "anm2ed"
#define ROUND_NEAREST_MULTIPLE(value, multiple) (roundf((value) / (multiple)) * (multiple)) #define ROUND_NEAREST_MULTIPLE(value, multiple) (roundf((value) / (multiple)) * (multiple))
#define FLOAT_TO_U8(x) (static_cast<u8>((x) * 255.0f)) #define FLOAT_TO_U8(x) (static_cast<u8>((x) * 255.0f))
#define U8_TO_FLOAT(x) ((x) / 255.0f) #define U8_TO_FLOAT(x) ((x) / 255.0f)
#define PERCENT_TO_UNIT(x) (x / 100.0f) #define PERCENT_TO_UNIT(x) (x / 100.0f)
#define TICK_DELAY 33.3f #define UNIT_TO_PERCENT(x) (x * 100.0f)
#define TICK_CATCH_UP_MAX (33.3f * 5)
#define SECOND 1000.0f #define SECOND 1000.0f
#define TICK_RATE (SECOND / TICK_DELAY) #define TICK_DELAY (SECOND / 30.0)
#define UPDATE_DELAY (SECOND / 120.0)
#define ID_NONE -1 #define ID_NONE -1
#define INDEX_NONE -1 #define INDEX_NONE -1
#define VALUE_NONE -1
#define TIME_NONE -1.0f #define TIME_NONE -1.0f
#define GL_ID_NONE 0
#if defined(_WIN32) #ifdef _WIN32
#define POPEN _popen #define POPEN _popen
#define PCLOSE _pclose #define PCLOSE _pclose
#define PWRITE_MODE "wb" #define PWRITE_MODE "wb"
#define PREAD_MODE "r"
#else #else
#define POPEN popen #define POPEN popen
#define PCLOSE pclose #define PCLOSE pclose
#define PWRITE_MODE "w" #define PWRITE_MODE "w"
#define PREAD_MODE "r"
#endif #endif
#define UV_VERTICES(uvMin, uvMax) \
{ \
0, 0, uvMin.x, uvMin.y, \
1, 0, uvMax.x, uvMin.y, \
1, 1, uvMax.x, uvMax.y, \
0, 1, uvMin.x, uvMax.y \
}
static const f32 GL_VERTICES[] =
{
0, 0,
1, 0,
1, 1,
0, 1
};
constexpr f32 GL_UV_VERTICES[] =
{
0, 0, 0.0f, 0.0f,
1, 0, 1.0f, 0.0f,
1, 1, 1.0f, 1.0f,
0, 1, 0.0f, 1.0f
};
static const GLuint GL_TEXTURE_INDICES[] = {0, 1, 2, 2, 3, 0}; 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_RED = {1.0f, 0.0f, 0.0f, 1.0f};
static const vec4 COLOR_GREEN = {0.0f, 1.0f, 0.0f, 1.0f}; static const vec4 COLOR_GREEN = {0.0f, 1.0f, 0.0f, 1.0f};
@@ -100,19 +82,12 @@ static const vec4 COLOR_OPAQUE = {1.0f, 1.0f, 1.0f, 1.0f};
static const vec4 COLOR_TRANSPARENT = {0.0f, 0.0f, 0.0f, 0.0f}; static const vec4 COLOR_TRANSPARENT = {0.0f, 0.0f, 0.0f, 0.0f};
static const vec3 COLOR_OFFSET_NONE = {0.0f, 0.0f, 0.0f}; static const vec3 COLOR_OFFSET_NONE = {0.0f, 0.0f, 0.0f};
static inline void log_error(const std::string& string) static inline std::string preferences_path_get(void)
{ {
std::println("[ERROR] {}", string); char* preferencesPath = SDL_GetPrefPath("", PREFERENCES_DIRECTORY);
} std::string preferencesPathString = preferencesPath;
SDL_free(preferencesPath);
static inline void log_info(const std::string& string) return preferencesPathString;
{
std::println("[INFO] {}", string);
}
static inline void log_warning(const std::string& string)
{
std::println("[WARNING] {}", string);
} }
static inline bool string_to_bool(const std::string& string) static inline bool string_to_bool(const std::string& string)
@@ -125,71 +100,85 @@ static inline bool string_to_bool(const std::string& string)
return lower == "true"; return lower == "true";
} }
static inline std::string path_canonical_resolve static inline std::string string_quote(const std::string& string)
(
const std::string& inputPath,
const std::string& basePath = std::filesystem::current_path().string()
)
{ {
auto strings_equal_ignore_case = [](std::string a, std::string b) { return "\"" + string + "\"";
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; static inline std::string string_to_lowercase(std::string string) {
std::replace(sanitized.begin(), sanitized.end(), '\\', '/'); std::transform
(
string.begin(), string.end(), string.begin(),
[](u8 character){ return std::tolower(character); }
);
return string;
}
std::filesystem::path normalizedPath = sanitized;
std::filesystem::path absolutePath = normalizedPath.is_absolute()
? normalizedPath
: (std::filesystem::path(basePath) / normalizedPath);
std::error_code error; #define FLOAT_FORMAT_MAX_DECIMALS 9
if (std::filesystem::exists(absolutePath, error)) { #define FLOAT_FORMAT_EPSILON 1e-9f
std::error_code canonicalError; static constexpr f32 FLOAT_FORMAT_POW10[] = {
std::filesystem::path canonicalPath = std::filesystem::weakly_canonical(absolutePath, canonicalError); 1.f,
return (canonicalError ? absolutePath : canonicalPath).generic_string(); 10.f,
} 100.f,
1000.f,
std::filesystem::path resolvedPath = absolutePath.root_path(); 10000.f,
std::filesystem::path remainingPath = absolutePath.relative_path(); 100000.f,
1000000.f,
for (const std::filesystem::path& segment : remainingPath) { 10000000.f,
std::filesystem::path candidatePath = resolvedPath / segment; 100000000.f,
if (std::filesystem::exists(candidatePath, error)) { 1000000000.f
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 s32 f32_decimals_needed(f32 value)
{
f32 integerPart = 0.f;
f32 fractionalPart = modff(value, &integerPart);
fractionalPart = fabsf(fractionalPart);
if (fractionalPart < FLOAT_FORMAT_EPSILON)
return 0;
for (s32 decimalCount = 1; decimalCount <= FLOAT_FORMAT_MAX_DECIMALS; ++decimalCount)
{
f32 scaledFraction = fractionalPart * FLOAT_FORMAT_POW10[decimalCount];
if (fabsf(scaledFraction - roundf(scaledFraction)) <
FLOAT_FORMAT_EPSILON * FLOAT_FORMAT_POW10[decimalCount])
{
return decimalCount;
}
}
return FLOAT_FORMAT_MAX_DECIMALS;
}
static inline const char* f32_format_get(f32 value)
{
static std::string formatString;
const s32 decimalCount = f32_decimals_needed(value);
formatString = (decimalCount == 0)
? "%.0f"
: ("%." + std::to_string(decimalCount) + "f");
return formatString.c_str();
}
static inline const char* vec2_format_get(const vec2& value)
{
static std::string formatString;
const s32 decimalCountX = f32_decimals_needed(value.x);
const s32 decimalCountY = f32_decimals_needed(value.y);
const s32 decimalCount = (decimalCountX > decimalCountY) ? decimalCountX : decimalCountY;
formatString = (decimalCount == 0)
? "%.0f"
: ("%." + std::to_string(decimalCount) + "f");
return formatString.c_str();
}
static inline std::string working_directory_from_file_set(const std::string& path) static inline std::string working_directory_from_file_set(const std::string& path)
{ {
std::filesystem::path filePath = path; std::filesystem::path filePath = path;
std::filesystem::path parentPath = filePath.parent_path(); std::filesystem::path parentPath = filePath.parent_path();
std::filesystem::current_path(parentPath); std::filesystem::current_path(parentPath);
return parentPath; return parentPath.string();
}; };
static inline std::string path_extension_change(const std::string& path, const std::string& extension) static inline std::string path_extension_change(const std::string& path, const std::string& extension)
@@ -218,7 +207,6 @@ static inline bool path_is_valid(const std::filesystem::path& pathCheck)
std::error_code ec; std::error_code ec;
if (fs::is_directory(pathCheck, ec)) return false; 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("."); fs::path parentDir = pathCheck.has_parent_path() ? pathCheck.parent_path() : fs::path(".");
if (!fs::is_directory(parentDir, ec)) return false; if (!fs::is_directory(parentDir, ec)) return false;
@@ -229,16 +217,11 @@ static inline bool path_is_valid(const std::filesystem::path& pathCheck)
testStream.close(); testStream.close();
if (!existedBefore && isValid) if (!existedBefore && isValid)
fs::remove(pathCheck, ec); // cleanup if we created it fs::remove(pathCheck, ec);
return isValid; 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) static inline s32 string_to_enum(const std::string& string, const char* const* array, s32 n)
{ {
for (s32 i = 0; i < n; i++) for (s32 i = 0; i < n; i++)
@@ -268,8 +251,10 @@ static inline s32 map_next_id_get(const std::map<s32, T>& map)
return id; 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()) if (auto it = map.find(id); it != map.end())
return &it->second; return &it->second;
@@ -324,7 +309,23 @@ static inline void map_insert_shift(std::map<int, T>& map, s32 index, const T& v
map[insertIndex] = value; map[insertIndex] = value;
} }
static inline mat4 quad_model_get(vec2 size, vec2 position, vec2 pivot, f32 rotation, vec2 scale) 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 scaleAbsolute = glm::abs(scale);
vec2 scaleSign = glm::sign(scale); vec2 scaleSign = glm::sign(scale);
@@ -341,7 +342,7 @@ static inline mat4 quad_model_get(vec2 size, vec2 position, vec2 pivot, f32 rota
return model; return model;
} }
static inline mat4 quad_parent_model_get(vec2 position, vec2 pivot, f32 rotation, vec2 scale) static inline mat4 quad_model_parent_get(vec2 position = {}, vec2 pivot = {}, vec2 scale = vec2(1.0f), f32 rotation = {})
{ {
vec2 scaleSign = glm::sign(scale); vec2 scaleSign = glm::sign(scale);
vec2 scaleAbsolute = glm::abs(scale); vec2 scaleAbsolute = glm::abs(scale);
@@ -349,7 +350,7 @@ static inline mat4 quad_parent_model_get(vec2 position, vec2 pivot, f32 rotation
mat4 local(1.0f); mat4 local(1.0f);
local = glm::translate(local, vec3(pivot, 0.0f)); local = glm::translate(local, vec3(pivot, 0.0f));
local = glm::scale(local, vec3(scaleSign, 1.0f)); // mirror if needed local = glm::scale(local, vec3(scaleSign, 1.0f));
local = glm::rotate(local, glm::radians(rotation) * handedness, vec3(0, 0, 1)); local = glm::rotate(local, glm::radians(rotation) * handedness, vec3(0, 0, 1));
local = glm::translate(local, vec3(-pivot, 0.0f)); local = glm::translate(local, vec3(-pivot, 0.0f));
local = glm::scale(local, vec3(scaleAbsolute, 1.0f)); local = glm::scale(local, vec3(scaleAbsolute, 1.0f));
@@ -357,31 +358,36 @@ static inline mat4 quad_parent_model_get(vec2 position, vec2 pivot, f32 rotation
return glm::translate(mat4(1.0f), vec3(position, 0.0f)) * local; return glm::translate(mat4(1.0f), vec3(position, 0.0f)) * local;
} }
#define DEFINE_ENUM_TO_STRING_FUNCTION(function, array, count) \
static inline std::string function(s32 index) \
{ \
return enum_to_string(array, count, index); \
};
#define DEFINE_STRING_TO_ENUM_FUNCTION(function, enumType, stringArray, count) \ #define DEFINE_STRING_TO_ENUM_FUNCTION(function, enumType, stringArray, count) \
static inline enumType function(const std::string& string) \ static inline enumType function(const std::string& string) \
{ \ { \
return static_cast<enumType>(string_to_enum(string, stringArray, count)); \ return static_cast<enumType>(string_to_enum(string, stringArray, count)); \
}; };
#define DATATYPE_LIST \
X(TYPE_INT, s32) \
X(TYPE_BOOL, bool) \
X(TYPE_FLOAT, f32) \
X(TYPE_STRING, std::string) \
X(TYPE_IVEC2, ivec2) \
X(TYPE_IVEC2_WH, ivec2) \
X(TYPE_VEC2, vec2) \
X(TYPE_VEC2_WH, vec2) \
X(TYPE_VEC3, vec3) \
X(TYPE_VEC4, vec4)
enum DataType enum DataType
{ {
TYPE_INT, #define X(symbol, ctype) symbol,
TYPE_BOOL, DATATYPE_LIST
TYPE_FLOAT, #undef X
TYPE_STRING,
TYPE_IVEC2,
TYPE_VEC2,
TYPE_VEC3,
TYPE_VEC4
}; };
#define DATATYPE_TO_CTYPE(dt) DATATYPE_CTYPE_##dt
#define X(symbol, ctype) typedef ctype DATATYPE_CTYPE_##symbol;
DATATYPE_LIST
#undef X
enum OriginType enum OriginType
{ {
ORIGIN_TOP_LEFT, ORIGIN_TOP_LEFT,

View File

@@ -1,5 +1,3 @@
// Includes packed resources (i.e., textures, shaders)
#pragma once #pragma once
#include "COMMON.h" #include "COMMON.h"
@@ -7,104 +5,113 @@
const u8 TEXTURE_ATLAS[] = const u8 TEXTURE_ATLAS[] =
{ {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x78, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0xa0,
0x04, 0x03, 0x00, 0x00, 0x00, 0xff, 0x33, 0xea, 0xfd, 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, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x12, 0x00, 0x00, 0x0b,
0x12, 0x01, 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x0f, 0x50, 0x4c, 0x12, 0x01, 0xd2, 0xdd, 0x7e, 0xfc, 0x00, 0x00, 0x00, 0x0f, 0x50, 0x4c,
0x54, 0x45, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x60, 0x60, 0x60, 0xff, 0x54, 0x45, 0x00, 0x00, 0x00, 0x76, 0x76, 0x76, 0xff, 0xff, 0xff, 0x60,
0xff, 0xff, 0x60, 0x60, 0x60, 0x15, 0x68, 0x14, 0xc2, 0x00, 0x00, 0x00, 0x60, 0x60, 0xff, 0xff, 0xff, 0x3e, 0xd5, 0x47, 0x6d, 0x00, 0x00, 0x00,
0x03, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x00, 0x00, 0xfa, 0x76, 0xc4, 0xde, 0x03, 0x74, 0x52, 0x4e, 0x53, 0x00, 0x00, 0x00, 0xfa, 0x76, 0xc4, 0xde,
0x00, 0x00, 0x03, 0xf4, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0xed, 0x98, 0x00, 0x00, 0x04, 0x62, 0x49, 0x44, 0x41, 0x54, 0x58, 0xc3, 0xed, 0x59,
0x6d, 0x6e, 0xde, 0x38, 0x0c, 0x84, 0x27, 0x20, 0x0f, 0xb0, 0xc1, 0x5e, 0x5b, 0x6e, 0x24, 0x37, 0x0c, 0x2c, 0x80, 0xbc, 0x40, 0xee, 0x90, 0x03,
0x60, 0x81, 0x5c, 0x80, 0x9b, 0xe1, 0x01, 0x08, 0x88, 0xf7, 0x3f, 0xd3, 0x10, 0x20, 0x0f, 0xc0, 0x80, 0x75, 0xff, 0x33, 0xe5, 0x83, 0xa2, 0x5a,
0x42, 0x94, 0x04, 0xa9, 0xad, 0x83, 0xd8, 0x0b, 0x34, 0xed, 0x8f, 0x4e, 0x63, 0xcf, 0xae, 0xa7, 0x37, 0xb1, 0x03, 0x6c, 0xac, 0x0f, 0x77, 0x6b,
0x90, 0xc8, 0xb4, 0xf9, 0x84, 0x5f, 0xd2, 0xeb, 0x20, 0xf8, 0x3f, 0x22, 0x5a, 0xd5, 0x7c, 0x15, 0x49, 0xb5, 0x0c, 0xfc, 0xc2, 0x20, 0x9f, 0xff,
0x71, 0x29, 0x31, 0x94, 0x9a, 0x97, 0xd7, 0xe1, 0x4f, 0xd6, 0x12, 0x42, 0xae, 0xd9, 0xd7, 0x28, 0x00, 0xe0, 0xb1, 0x9e, 0x04, 0x40, 0x9a, 0x92,
0xc6, 0xe1, 0x4f, 0xda, 0xf1, 0x0b, 0x99, 0x0e, 0x68, 0xdd, 0x07, 0x68, 0x76, 0xac, 0x27, 0xf3, 0x78, 0x21, 0xbd, 0x00, 0x01, 0xa0, 0x04, 0x98,
0x90, 0x22, 0x87, 0x4b, 0x23, 0x09, 0x29, 0x73, 0x03, 0x29, 0x04, 0x64, 0xd0, 0x46, 0x26, 0x00, 0x04, 0x49, 0xe8, 0x9e, 0x36, 0xc0, 0x95, 0x80,
0x86, 0x22, 0xeb, 0x72, 0x7b, 0x90, 0xc2, 0x92, 0xed, 0x94, 0x84, 0xae, 0x2e, 0x51, 0x64, 0xdf, 0xee, 0x15, 0xa4, 0x92, 0x83, 0x58, 0x2a, 0x29,
0x1c, 0x80, 0x90, 0x38, 0x1c, 0xd4, 0x80, 0xd3, 0x16, 0x9b, 0x00, 0xc9, 0x4b, 0xd8, 0x00, 0x9d, 0x17, 0xf6, 0x02, 0xc9, 0x56, 0x68, 0xe6, 0x6d,
0x61, 0xd1, 0xc4, 0xa4, 0xb2, 0xd7, 0xed, 0x30, 0x53, 0xba, 0x02, 0x84, 0x4b, 0xbf, 0xa2, 0x67, 0x4c, 0x4d, 0x35, 0xec, 0xb5, 0x89, 0x41, 0x24,
0x10, 0x13, 0x94, 0xac, 0xdb, 0x15, 0x26, 0x1b, 0xe9, 0x19, 0xdf, 0x02, 0x9e, 0x01, 0x94, 0xd0, 0xd4, 0xb6, 0x34, 0x35, 0x7b, 0x81, 0x78, 0x90,
0x5e, 0x16, 0xed, 0x04, 0x1c, 0x2d, 0x0a, 0xc8, 0xe6, 0xd9, 0x01, 0x1d, 0xe5, 0xf6, 0x08, 0x28, 0x40, 0x13, 0xcc, 0x13, 0x50, 0x08, 0x03, 0x20,
0xbd, 0x63, 0x0b, 0xd2, 0x3a, 0x20, 0xc4, 0x01, 0x28, 0x41, 0x60, 0x10, 0xee, 0x51, 0xee, 0x06, 0x48, 0xfb, 0x8e, 0x61, 0x64, 0x02, 0x9a, 0x4a,
0xe5, 0x8f, 0x66, 0xe2, 0x1d, 0x58, 0xd5, 0x17, 0x46, 0x2c, 0x00, 0x0b, 0x1c, 0x00, 0xe1, 0xf2, 0xb8, 0xb8, 0xbb, 0x1b, 0x80, 0x48, 0xad, 0xb6,
0x88, 0xee, 0x1f, 0xa1, 0x64, 0xcd, 0x08, 0x3c, 0xe7, 0x7c, 0x02, 0x8d, 0x75, 0x59, 0xcf, 0x04, 0xda, 0x4b, 0x00, 0x12, 0x03, 0x30, 0x77, 0x37,
0x5e, 0x01, 0x54, 0x35, 0x55, 0x21, 0x95, 0x06, 0x6d, 0x02, 0x7b, 0x70, 0x33, 0x21, 0x01, 0xd2, 0xae, 0xc0, 0x75, 0xd4, 0x2e, 0x40, 0xb0, 0x00,
0xab, 0x4b, 0xf5, 0xb0, 0x14, 0x5d, 0x17, 0xc0, 0xdc, 0x1a, 0xca, 0x52, 0x40, 0x44, 0xc4, 0x45, 0xa0, 0x4c, 0x40, 0x99, 0x17, 0x60, 0x02, 0x37,
0xa0, 0x9e, 0xcc, 0x18, 0x00, 0x94, 0x31, 0x7e, 0xf0, 0xf3, 0xcd, 0x87, 0x5e, 0xd2, 0x15, 0x5f, 0x98, 0x99, 0xd9, 0x13, 0xc0, 0xa2, 0x86, 0xb4,
0xc0, 0x2f, 0x96, 0x92, 0x71, 0x65, 0x8b, 0x61, 0xd9, 0xd8, 0xda, 0xe7, 0xdf, 0xed, 0x22, 0x8d, 0x88, 0x00, 0x10, 0x5a, 0xff, 0xe1, 0xc7, 0xe4,
0xa2, 0x71, 0x6c, 0x81, 0xec, 0x6b, 0x1e, 0x00, 0xb9, 0x89, 0xa3, 0xbd, 0x83, 0xe1, 0x8b, 0xc6, 0xd0, 0x5c, 0xde, 0x48, 0x94, 0x07, 0x72, 0x5f,
0x70, 0x74, 0xaa, 0x83, 0x40, 0x07, 0x85, 0x7c, 0x07, 0x80, 0x37, 0x66, 0xf3, 0xa1, 0xf9, 0xdb, 0xe7, 0x93, 0x17, 0xc1, 0xa6, 0x80, 0x93, 0xa4,
0xd0, 0x8e, 0xd0, 0xcc, 0x4c, 0x12, 0xb0, 0xa2, 0x01, 0xc6, 0xe8, 0x9f, 0x5f, 0x9c, 0x92, 0xe5, 0x13, 0xbc, 0x71, 0x2f, 0x0a, 0x00, 0x02, 0x08,
0x8b, 0xb5, 0x0a, 0xa0, 0x91, 0xe4, 0xf0, 0xef, 0x84, 0x32, 0xb3, 0x91, 0x02, 0xac, 0xcd, 0x5a, 0x09, 0xba, 0xb3, 0xec, 0x12, 0x45, 0x77, 0x27,
0xb1, 0x00, 0x25, 0x20, 0x65, 0x8b, 0xc9, 0x04, 0xa4, 0x00, 0xf1, 0xcc, 0x81, 0x26, 0x28, 0x40, 0xeb, 0x3c, 0x19, 0x40, 0x3f, 0xb7, 0x2d, 0x4a,
0x66, 0xb5, 0xd0, 0x9b, 0x2d, 0x60, 0x9d, 0x13, 0x96, 0x80, 0x56, 0xcf, 0xe8, 0x1e, 0xa4, 0x0d, 0x40, 0x3a, 0x4f, 0x6c, 0xab, 0xd4, 0xcf, 0x01,
0xc5, 0xa0, 0xde, 0x9a, 0x7b, 0x0b, 0xf1, 0x6c, 0xcc, 0x13, 0x80, 0x10, 0x68, 0xb9, 0x47, 0x42, 0xcb, 0x9d, 0x15, 0x39, 0x80, 0xc9, 0x93, 0xa1,
0x07, 0x50, 0xcf, 0xc5, 0xc0, 0x10, 0x31, 0x53, 0x36, 0x4b, 0x7a, 0x8a, 0xf9, 0x7e, 0x2e, 0x15, 0x51, 0x15, 0xa6, 0xe5, 0x41, 0x8f, 0x44, 0x42,
0x0f, 0x40, 0xb1, 0xce, 0xc9, 0x4a, 0x49, 0x4c, 0xe9, 0xdd, 0x56, 0x93, 0xb3, 0x01, 0x4a, 0x68, 0x0e, 0xcd, 0xf7, 0x73, 0x9a, 0x6a, 0xa6, 0x30,
0xe8, 0xdf, 0xe2, 0x42, 0x60, 0x45, 0xd8, 0xe7, 0x64, 0x15, 0xdd, 0x9f, 0xd2, 0x59, 0xae, 0xd5, 0x00, 0xd9, 0x79, 0x32, 0x34, 0xd7, 0x72, 0x96,
0x67, 0xb7, 0x21, 0x20, 0x61, 0x50, 0xd2, 0x33, 0x67, 0xd1, 0x64, 0xb7, 0x47, 0x42, 0x52, 0x4d, 0x52, 0x4d, 0x4b, 0x09, 0x8c, 0x4a, 0x57, 0x9e,
0x0d, 0xc9, 0xa8, 0xb6, 0x92, 0x00, 0xe9, 0x91, 0xd5, 0x25, 0x47, 0x1d, 0xc0, 0xdc, 0x1d, 0x80, 0x66, 0xd0, 0xa1, 0x09, 0x28, 0x48, 0x24, 0x84,
0x4a, 0x56, 0x5f, 0xd1, 0x01, 0x69, 0x6e, 0x05, 0x75, 0x00, 0x62, 0xd3, 0x2c, 0xf7, 0x65, 0x34, 0x09, 0x21, 0x13, 0x4e, 0x53, 0x29, 0xb1, 0x9e,
0x8e, 0xb2, 0x6b, 0xb4, 0x5e, 0x83, 0xcb, 0x9c, 0x83, 0xab, 0x2c, 0x1a, 0x97, 0x39, 0x01, 0x48, 0x01, 0x61, 0x00, 0xe9, 0xee, 0xed, 0x56, 0x8d,
0x91, 0xf4, 0xec, 0x40, 0xd9, 0xc2, 0x08, 0xba, 0x5e, 0x6d, 0x8d, 0xe8, 0x4a, 0x90, 0x10, 0xa7, 0x81, 0x66, 0x6b, 0x6e, 0xe2, 0xb4, 0x0e, 0x6d,
0x55, 0x21, 0xcb, 0x0e, 0xe0, 0xf5, 0x9f, 0xb2, 0x31, 0xec, 0xeb, 0xcd, 0x01, 0x50, 0xba, 0xaf, 0xc0, 0x95, 0x66, 0x20, 0x08, 0x67, 0x39, 0x28,
0xb7, 0x26, 0x9a, 0x79, 0xd8, 0x58, 0xf6, 0x73, 0x89, 0xed, 0xf5, 0x96, 0xc7, 0x3c, 0xfd, 0x19, 0x35, 0x4c, 0x4d, 0xb2, 0xa9, 0x01, 0x53, 0x24,
0xc8, 0xbd, 0xee, 0xd4, 0xc4, 0x28, 0xc6, 0xbd, 0x96, 0x32, 0xf6, 0x7b, 0xd4, 0x24, 0x05, 0x24, 0xf9, 0xc0, 0xbf, 0x83, 0x5c, 0x64, 0xcf, 0xdd,
0xa0, 0xd6, 0x5d, 0xbc, 0xac, 0xf0, 0x38, 0xa4, 0x05, 0x9c, 0xa9, 0xfd, 0xcf, 0x8c, 0xb9, 0xe6, 0xbf, 0xca, 0xf7, 0xb9, 0xbe, 0xdc, 0x1f, 0xd6,
0x05, 0xfc, 0x4d, 0x76, 0x52, 0xc6, 0x6f, 0xb0, 0x09, 0x18, 0x5e, 0x01, 0x75, 0xa9, 0xa6, 0x49, 0x4d, 0x5e, 0x57, 0x00, 0x40, 0x57, 0xc2, 0x29,
0x89, 0x8c, 0xb3, 0x3b, 0x52, 0x77, 0xc9, 0x58, 0x87, 0xb1, 0x03, 0x98, 0xa1, 0x47, 0xed, 0xc6, 0x94, 0x9d, 0x7d, 0x5d, 0xc5, 0xcd, 0xde, 0xab,
0xc0, 0xcb, 0x2b, 0xe0, 0x54, 0x00, 0xd6, 0x42, 0x5d, 0x00, 0xeb, 0x80, 0xb6, 0xd8, 0xa9, 0xfd, 0x86, 0x5d, 0x11, 0x01, 0x40, 0xcd, 0xed, 0xf4,
0x60, 0x7d, 0x70, 0x49, 0x8d, 0xc4, 0xce, 0x94, 0xa4, 0x8d, 0xc1, 0x3b, 0xce, 0x2a, 0xba, 0xec, 0x6a, 0xd7, 0x45, 0x71, 0xcc, 0x4a, 0x00, 0x28,
0xd0, 0xc4, 0xc4, 0xd0, 0x1c, 0x62, 0x52, 0xe1, 0x06, 0x50, 0x29, 0xed, 0x0a, 0x80, 0x0c, 0x93, 0x52, 0x6c, 0x96, 0xae, 0xc2, 0xa5, 0x00, 0xa2,
0x08, 0x4e, 0x37, 0xa0, 0xdf, 0x33, 0x11, 0x2e, 0x09, 0x51, 0x40, 0x75, 0x25, 0x6d, 0x0d, 0x82, 0x06, 0x68, 0x16, 0x10, 0x9a, 0x9a, 0x88, 0x82,
0xa1, 0xed, 0x94, 0xf0, 0x2a, 0x8d, 0x0d, 0xd7, 0x80, 0x81, 0x26, 0x45, 0xa6, 0x76, 0xfa, 0xa5, 0x2e, 0x09, 0x87, 0x4a, 0xc5, 0x4a, 0x80, 0x99,
0x9d, 0x29, 0x55, 0x80, 0xeb, 0x94, 0x28, 0x34, 0x61, 0x55, 0x6e, 0x05, 0x9a, 0xba, 0x12, 0x91, 0xa4, 0x76, 0x8c, 0xba, 0x09, 0xc5, 0xa9, 0x92,
0x88, 0x19, 0x50, 0x01, 0x7e, 0x2c, 0x5a, 0x69, 0x20, 0x19, 0x73, 0x2a, 0x06, 0x03, 0xcf, 0x01, 0xd9, 0x12, 0x98, 0x38, 0x55, 0x6a, 0x01, 0x4f,
0xb6, 0xfa, 0x07, 0x91, 0x0a, 0x70, 0xd5, 0x56, 0x23, 0xb9, 0xc7, 0xb9, 0x55, 0xa2, 0x32, 0xb5, 0x99, 0xd5, 0x2a, 0x69, 0x26, 0xd0, 0x02, 0xde,
0x46, 0x4a, 0xab, 0x00, 0x57, 0x83, 0x33, 0xd2, 0x16, 0x60, 0x1b, 0x20, 0x19, 0x2d, 0x4c, 0xb0, 0x7b, 0x76, 0x8e, 0x77, 0x3a, 0x6f, 0xb4, 0xf2,
0x4a, 0x17, 0x5b, 0x63, 0x47, 0x38, 0x52, 0xd2, 0x66, 0x57, 0x9b, 0x6f, 0x0d, 0xe9, 0xc6, 0xad, 0xb9, 0xda, 0xf4, 0x14, 0x9c, 0xbe, 0x67, 0xc6,
0x5d, 0xd0, 0x3a, 0x40, 0x31, 0x80, 0x52, 0xab, 0xc6, 0xe5, 0xf6, 0xfe, 0x73, 0x4e, 0x29, 0x93, 0xcc, 0x01, 0xe4, 0x05, 0xe0, 0xdb, 0x2d, 0xc2,
0x1a, 0xd9, 0x11, 0x1b, 0x43, 0x4a, 0x4c, 0xc5, 0xb0, 0xc7, 0x22, 0xd1, 0xa6, 0xc6, 0x29, 0x61, 0xab, 0x24, 0x91, 0x3f, 0x22, 0x5f, 0xef, 0x08,
0x7c, 0x27, 0x76, 0x24, 0xc9, 0x5c, 0x40, 0x58, 0x2d, 0xd3, 0x56, 0x27, 0x94, 0x4c, 0x6a, 0x02, 0xd4, 0xa4, 0xe6, 0x43, 0xe9, 0xbe, 0x4d, 0xef,
0x01, 0x9e, 0x00, 0x01, 0x88, 0x33, 0x46, 0x18, 0x9f, 0x0f, 0xb5, 0x4d, 0x7f, 0x3a, 0x0e, 0x39, 0xba, 0xb3, 0xf7, 0xb1, 0x8b, 0x2d, 0xfd, 0xd4,
0xa7, 0x56, 0xed, 0xec, 0x8a, 0xf5, 0x1e, 0x36, 0xb0, 0xf9, 0x4c, 0xeb, 0xa2, 0x1e, 0x15, 0x5b, 0xf7, 0xf4, 0x01, 0x58, 0xff, 0xb0, 0xe6, 0x52,
0xfd, 0x2d, 0x86, 0x63, 0x92, 0x3e, 0x32, 0xa6, 0x83, 0x43, 0xc0, 0xba, 0x5c, 0x3d, 0xe2, 0xec, 0x19, 0xd0, 0xea, 0x38, 0x09, 0x6b, 0x3d, 0x94,
0x60, 0xa0, 0xd4, 0xf2, 0xfd, 0x2d, 0x19, 0x23, 0x00, 0x73, 0x54, 0x2b, 0x58, 0x8b, 0x62, 0xf7, 0x83, 0x69, 0x03, 0x4c, 0xb0, 0x37, 0x0c, 0x10,
0x01, 0xf9, 0x16, 0x30, 0x24, 0xfb, 0x17, 0x94, 0x49, 0x66, 0xc1, 0x41, 0xa6, 0x5a, 0x2f, 0xf4, 0xae, 0x64, 0x50, 0xb0, 0xb0, 0x38, 0x88, 0x7d,
0x7a, 0x54, 0x00, 0xa8, 0x03, 0xb4, 0xfd, 0x17, 0x8c, 0x10, 0x03, 0x98, 0x33, 0xb5, 0x27, 0x3c, 0xb5, 0x2b, 0x9f, 0x04, 0xe9, 0x6d, 0xad, 0xda,
0xa9, 0xd6, 0xb4, 0x95, 0xb2, 0x02, 0x84, 0xb5, 0xd9, 0x19, 0xb1, 0xb9, 0x74, 0x9c, 0x01, 0x24, 0x9c, 0x4e, 0x27, 0x84, 0x4e, 0x76, 0x4a, 0x1b,
0xcc, 0x46, 0x2a, 0x04, 0x1d, 0x18, 0x95, 0xda, 0x0a, 0x20, 0xe8, 0x3a, 0xbb, 0x75, 0x69, 0xd7, 0xd9, 0xde, 0xb5, 0xf5, 0x0e, 0x46, 0x89, 0x01,
0x01, 0x00, 0x59, 0x2a, 0xc0, 0xd7, 0x3b, 0x51, 0x57, 0x00, 0x94, 0x26, 0x28, 0x49, 0x46, 0x02, 0x10, 0xea, 0x08, 0xb0, 0x8c, 0xe5, 0x19, 0x4d,
0xb0, 0x47, 0xa5, 0x50, 0x08, 0x99, 0x05, 0x60, 0x34, 0x11, 0xc7, 0x69, 0x9d, 0xbd, 0x59, 0x1b, 0x0c, 0x05, 0xd9, 0xfc, 0x0e, 0xe6, 0x08, 0xd0,
0xdc, 0x35, 0x9c, 0x80, 0x49, 0xfa, 0xda, 0xd5, 0x18, 0x80, 0x61, 0x48, 0x1d, 0x87, 0x01, 0x00, 0x70, 0x77, 0x77, 0x87, 0x82, 0xac, 0x55, 0x41,
0x56, 0x97, 0x6c, 0x03, 0x75, 0xbb, 0x0d, 0x0f, 0x7a, 0x2a, 0x02, 0x8d, 0x29, 0x23, 0xe0, 0x08, 0xc0, 0x0e, 0x87, 0x01, 0x02, 0x81, 0x92, 0x5e,
0xb8, 0x04, 0xb2, 0x04, 0x40, 0x17, 0xa0, 0x19, 0x11, 0x38, 0x24, 0x6b, 0xab, 0xc4, 0xb6, 0x1f, 0x1e, 0x0a, 0xc4, 0xb2, 0xe1, 0x04, 0xa4, 0x7a,
0xd2, 0x86, 0x2f, 0x95, 0x1c, 0xf1, 0xd4, 0x6e, 0x01, 0x1b, 0x62, 0x00, 0x0d, 0xab, 0x97, 0x26, 0x79, 0xf1, 0x9d, 0xd7, 0x26, 0x76, 0x01, 0xba,
0x2f, 0x9c, 0x86, 0x30, 0x84, 0x31, 0xba, 0xf1, 0x01, 0x20, 0x44, 0x07, 0x26, 0x74, 0xe0, 0xcb, 0x05, 0x86, 0x38, 0x13, 0xe4, 0x00, 0x2c, 0x1b,
0x68, 0x9f, 0x01, 0xfb, 0x73, 0x65, 0x00, 0xb4, 0x7b, 0x80, 0x10, 0x13, 0x00, 0x19, 0x80, 0xb8, 0xd9, 0x63, 0xc9, 0x9c, 0x48, 0x7f, 0x15, 0xdd,
0xa0, 0x5d, 0x03, 0x27, 0x54, 0xf7, 0x63, 0x01, 0xb4, 0x0d, 0x94, 0xed, 0xf1, 0x3e, 0xbd, 0x24, 0x5f, 0x04, 0x0c, 0x68, 0x95, 0xd3, 0xb1, 0xc0,
0x6b, 0x3d, 0x6a, 0x50, 0x92, 0x5c, 0x00, 0xe3, 0x63, 0xe0, 0xdc, 0x39, 0xba, 0xbe, 0x82, 0xf5, 0x03, 0xc0, 0x2a, 0xa7, 0xcc, 0x8f, 0x00, 0x57,
0xbe, 0x00, 0xff, 0x10, 0x38, 0x95, 0xb4, 0x09, 0x78, 0xec, 0x94, 0x32, 0x5d, 0x99, 0x52, 0xf1, 0x1a, 0x40, 0xb9, 0xdb, 0x74, 0xfe, 0x04, 0x70,
0xe9, 0x99, 0x40, 0x96, 0xf6, 0x07, 0xf2, 0x6e, 0x6b, 0xf9, 0x7f, 0x54, 0xd4, 0xc6, 0xb2, 0x01, 0x74, 0x33, 0x69, 0x00, 0xd7, 0xde, 0x6f, 0xae,
0x74, 0x01, 0x67, 0x5b, 0x81, 0xf2, 0xbf, 0x05, 0x28, 0xb1, 0x9a, 0x75, 0xdb, 0x06, 0xd9, 0x1f, 0x4d, 0x9d, 0x9b, 0x3f, 0x03, 0x6c, 0x22, 0xd4,
0x0f, 0x90, 0xe1, 0x98, 0x81, 0xb5, 0xdb, 0x75, 0x5c, 0x67, 0x5e, 0x01, 0x00, 0xea, 0xc7, 0x80, 0x63, 0x38, 0x73, 0x01, 0xca, 0x2e, 0x95, 0xdc,
0x37, 0xb5, 0x8b, 0x7e, 0xac, 0xfc, 0x4e, 0x81, 0xef, 0x44, 0xbb, 0x06, 0x59, 0xee, 0x57, 0x3e, 0x1c, 0x05, 0x79, 0xdc, 0xba, 0x32, 0xf5, 0xb9,
0x56, 0x05, 0x9f, 0x02, 0x8d, 0x25, 0xff, 0x3a, 0x00, 0x7f, 0x80, 0xdf, 0xd1, 0xba, 0xea, 0xed, 0xe9, 0xd6, 0x36, 0xfe, 0x63, 0xc0, 0xaa, 0x83,
0x12, 0x20, 0x69, 0x4f, 0x00, 0x76, 0xd9, 0x7d, 0x40, 0x08, 0x9a, 0xf0, 0x6b, 0xfd, 0x0b, 0x80, 0xfe, 0xd4, 0xc0, 0xec, 0x0a, 0xc4, 0x6d, 0xed,
0x01, 0x60, 0xa0, 0xbd, 0x3c, 0x00, 0x58, 0x45, 0x8b, 0xfd, 0x0c, 0x80, 0x10, 0x1e, 0x36, 0x3b, 0xa7, 0x4a, 0xaf, 0x91, 0xf5, 0xb1, 0x0b, 0xdd,
0x3f, 0xea, 0xdf, 0x2f, 0x05, 0xbe, 0xbe, 0xe8, 0xe7, 0x73, 0x78, 0x3e, 0x18, 0xfe, 0x66, 0xd8, 0xfb, 0x57, 0x3f, 0x07, 0x8c, 0x05, 0x1f, 0x02,
0xe9, 0xe7, 0x7b, 0x69, 0x13, 0xf6, 0xe7, 0x4c, 0x3f, 0x06, 0x94, 0xa5, 0x82, 0x13, 0xe2, 0xaf, 0x02, 0xe0, 0x1b, 0xf0, 0x7b, 0x00, 0xd6, 0xde,
0xb8, 0x05, 0xec, 0x10, 0x8e, 0xfb, 0x80, 0x56, 0x00, 0xdc, 0x7e, 0x3f, 0xef, 0x65, 0x80, 0xae, 0xae, 0xfd, 0x32, 0x80, 0xa9, 0x7c, 0xd8, 0x7e,
0x54, 0x08, 0xc7, 0x13, 0x40, 0xc9, 0x78, 0x04, 0xa0, 0x39, 0x9e, 0x01, 0x7c, 0x08, 0xe8, 0xcf, 0xbf, 0x97, 0x01, 0xfd, 0xbd, 0x78, 0x9e, 0x07,
0xe5, 0x94, 0x53, 0x9f, 0xfd, 0x33, 0xf0, 0x3f, 0x53, 0x26, 0x87, 0x23, 0xfc, 0x7b, 0x00, 0x3e, 0x19, 0xff, 0x31, 0xe0, 0xf3, 0x8d, 0xfe, 0xc5,
0x59, 0xac, 0x2b, 0x06, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0x38, 0xdc, 0x8f, 0xf4, 0x6d, 0x2e, 0xdd, 0x66, 0xeb, 0xef, 0x99, 0xd3,
0xae, 0x42, 0x60, 0x82 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); const u32 TEXTURE_ATLAS_LENGTH = (u32)std::size(TEXTURE_ATLAS);
const vec2 TEXTURE_ATLAS_SIZE = {96, 120}; const vec2 TEXTURE_ATLAS_SIZE = {96, 160};
enum AtlasType enum AtlasType
{ {
@@ -118,8 +125,8 @@ enum AtlasType
ATLAS_INVISIBLE, ATLAS_INVISIBLE,
ATLAS_SHOW_RECT, ATLAS_SHOW_RECT,
ATLAS_HIDE_RECT, ATLAS_HIDE_RECT,
ATLAS_SHOW_TARGETS, ATLAS_SHOW_UNUSED,
ATLAS_HIDE_TARGETS, ATLAS_HIDE_UNUSED,
ATLAS_PAN, ATLAS_PAN,
ATLAS_MOVE, ATLAS_MOVE,
ATLAS_ROTATE, ATLAS_ROTATE,
@@ -145,6 +152,7 @@ enum AtlasType
ATLAS_FRAME, ATLAS_FRAME,
ATLAS_FRAME_ALT, ATLAS_FRAME_ALT,
ATLAS_TARGET, ATLAS_TARGET,
ATLAS_TARGET_ALT,
ATLAS_COUNT ATLAS_COUNT
}; };
@@ -197,7 +205,8 @@ const inline AtlasEntry ATLAS_ENTRIES[ATLAS_COUNT] =
{{ 0, 80}, ATLAS_SIZE_OBLONG}, {{ 0, 80}, ATLAS_SIZE_OBLONG},
{{16, 80}, ATLAS_SIZE_OBLONG}, {{16, 80}, ATLAS_SIZE_OBLONG},
{{32, 80}, ATLAS_SIZE_OBLONG}, {{32, 80}, ATLAS_SIZE_OBLONG},
{{48, 80}, ATLAS_SIZE_BIG} {{48, 80}, ATLAS_SIZE_BIG},
{{48, 120}, ATLAS_SIZE_BIG}
}; };
#define ATLAS_POSITION(type) ATLAS_ENTRIES[type].position #define ATLAS_POSITION(type) ATLAS_ENTRIES[type].position
@@ -217,6 +226,7 @@ enum ShaderType
{ {
SHADER_LINE, SHADER_LINE,
SHADER_TEXTURE, SHADER_TEXTURE,
SHADER_AXIS,
SHADER_GRID, SHADER_GRID,
SHADER_COUNT SHADER_COUNT
}; };
@@ -234,6 +244,33 @@ void main()
} }
)"; )";
const std::string SHADER_AXIS_VERTEX = R"(
#version 330 core
layout (location = 0) in vec2 i_position; // full screen line segment: -1..1
uniform vec2 u_origin_ndc; // world origin in NDC
uniform int u_axis; // 0 = X axis, 1 = Y axis
void main()
{
vec2 pos = (u_axis == 0)
? vec2(i_position.x, u_origin_ndc.y) // horizontal line across screen
: vec2(u_origin_ndc.x, i_position.x); // vertical line across screen;
gl_Position = vec4(pos, 0.0, 1.0);
}
)";
const std::string SHADER_GRID_VERTEX = R"(
#version 330 core
layout ( location = 0 ) in vec2 i_position;
out vec2 clip;
void main() {
clip = i_position;
gl_Position = vec4(i_position, 0.0, 1.0);
}
)";
const std::string SHADER_FRAGMENT = R"( const std::string SHADER_FRAGMENT = R"(
#version 330 core #version 330 core
out vec4 o_fragColor; out vec4 o_fragColor;
@@ -260,36 +297,23 @@ void main()
} }
)"; )";
const std::string SHADER_GRID_VERTEX = R"(
#version 330 core
layout ( location = 0 ) in vec2 i_position;
out vec2 clip;
void main() {
clip = i_position;
gl_Position = vec4(i_position, 0.0, 1.0);
}
)";
const std::string SHADER_GRID_FRAGMENT = R"( const std::string SHADER_GRID_FRAGMENT = R"(
#version 330 core #version 330 core
in vec2 clip; in vec2 clip;
uniform mat4 u_model; // inverse of your world->clip matrix (MVP) uniform mat4 u_model;
uniform vec2 u_size; // world-space cell size (e.g. 64,64) uniform vec2 u_size;
uniform vec2 u_offset; // world-space grid offset (shifts entire grid) uniform vec2 u_offset;
uniform vec4 u_color; // RGBA uniform vec4 u_color;
out vec4 o_fragColor; out vec4 o_fragColor;
void main() void main()
{ {
// clip -> world on z=0 plane
vec4 w = u_model * vec4(clip, 0.0, 1.0); vec4 w = u_model * vec4(clip, 0.0, 1.0);
w /= w.w; w /= w.w;
vec2 world = w.xy; vec2 world = w.xy;
// grid space
vec2 g = (world - u_offset) / u_size; vec2 g = (world - u_offset) / u_size;
vec2 d = abs(fract(g) - 0.5); vec2 d = abs(fract(g) - 0.5);
@@ -303,12 +327,13 @@ void main()
} }
)"; )";
#define SHADER_UNIFORM_AXIS "u_axis"
#define SHADER_UNIFORM_COLOR "u_color" #define SHADER_UNIFORM_COLOR "u_color"
#define SHADER_UNIFORM_TRANSFORM "u_transform" #define SHADER_UNIFORM_TRANSFORM "u_transform"
#define SHADER_UNIFORM_TINT "u_tint" #define SHADER_UNIFORM_TINT "u_tint"
#define SHADER_UNIFORM_COLOR_OFFSET "u_color_offset" #define SHADER_UNIFORM_COLOR_OFFSET "u_color_offset"
#define SHADER_UNIFORM_OFFSET "u_offset" #define SHADER_UNIFORM_OFFSET "u_offset"
#define SHADER_UNIFORM_ORIGIN_NDC "u_origin_ndc"
#define SHADER_UNIFORM_SIZE "u_size" #define SHADER_UNIFORM_SIZE "u_size"
#define SHADER_UNIFORM_MODEL "u_model" #define SHADER_UNIFORM_MODEL "u_model"
#define SHADER_UNIFORM_TEXTURE "u_texture" #define SHADER_UNIFORM_TEXTURE "u_texture"
@@ -317,5 +342,6 @@ const ShaderData SHADER_DATA[SHADER_COUNT] =
{ {
{SHADER_VERTEX, SHADER_FRAGMENT}, {SHADER_VERTEX, SHADER_FRAGMENT},
{SHADER_VERTEX, SHADER_TEXTURE_FRAGMENT}, {SHADER_VERTEX, SHADER_TEXTURE_FRAGMENT},
{SHADER_AXIS_VERTEX, SHADER_FRAGMENT},
{SHADER_GRID_VERTEX, SHADER_GRID_FRAGMENT} {SHADER_GRID_VERTEX, SHADER_GRID_FRAGMENT}
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
#pragma once #pragma once
#include "COMMON.h"
#include "resources.h" #include "resources.h"
#define ANM2_SCALE_CONVERT(x) ((f32)x / 100.0f) #define ANM2_SCALE_CONVERT(x) ((f32)x / 100.0f)
@@ -14,7 +13,11 @@
#define ANM2_FRAME_DELAY_MIN 1 #define ANM2_FRAME_DELAY_MIN 1
#define ANM2_STRING_MAX 0xFF #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_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_READ_INFO "Read anm2 from file: {}"
#define ANM2_WRITE_ERROR "Failed to write anm2 to file: {}" #define ANM2_WRITE_ERROR "Failed to write anm2 to file: {}"
#define ANM2_WRITE_INFO "Wrote anm2 to file: {}" #define ANM2_WRITE_INFO "Wrote anm2 to file: {}"
@@ -23,7 +26,6 @@
#define ANM2_EXTENSION "anm2" #define ANM2_EXTENSION "anm2"
#define ANM2_SPRITESHEET_EXTENSION "png" #define ANM2_SPRITESHEET_EXTENSION "png"
/* Elements */
#define ANM2_ELEMENT_LIST \ #define ANM2_ELEMENT_LIST \
X(ANIMATED_ACTOR, "AnimatedActor") \ X(ANIMATED_ACTOR, "AnimatedActor") \
X(INFO, "Info") \ X(INFO, "Info") \
@@ -47,21 +49,22 @@
X(TRIGGERS, "Triggers") \ X(TRIGGERS, "Triggers") \
X(TRIGGER, "Trigger") X(TRIGGER, "Trigger")
typedef enum { typedef enum
{
#define X(name, str) ANM2_ELEMENT_##name, #define X(name, str) ANM2_ELEMENT_##name,
ANM2_ELEMENT_LIST ANM2_ELEMENT_LIST
#undef X #undef X
ANM2_ELEMENT_COUNT ANM2_ELEMENT_COUNT
} Anm2Element; } Anm2Element;
static const char* ANM2_ELEMENT_STRINGS[] = { const inline char* ANM2_ELEMENT_STRINGS[] =
{
#define X(name, str) str, #define X(name, str) str,
ANM2_ELEMENT_LIST ANM2_ELEMENT_LIST
#undef X #undef X
}; };
DEFINE_STRING_TO_ENUM_FUNCTION(ANM2_ELEMENT_STRING_TO_ENUM, Anm2Element, ANM2_ELEMENT_STRINGS, ANM2_ELEMENT_COUNT) 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 \ #define ANM2_ATTRIBUTE_LIST \
X(CREATED_BY, "CreatedBy") \ X(CREATED_BY, "CreatedBy") \
@@ -102,21 +105,22 @@ DEFINE_ENUM_TO_STRING_FUNCTION(ANM2_ELEMENT_ENUM_TO_STRING, ANM2_ELEMENT_STRINGS
X(EVENT_ID, "EventId") \ X(EVENT_ID, "EventId") \
X(AT_FRAME, "AtFrame") X(AT_FRAME, "AtFrame")
typedef enum { typedef enum
{
#define X(name, str) ANM2_ATTRIBUTE_##name, #define X(name, str) ANM2_ATTRIBUTE_##name,
ANM2_ATTRIBUTE_LIST ANM2_ATTRIBUTE_LIST
#undef X #undef X
ANM2_ATTRIBUTE_COUNT ANM2_ATTRIBUTE_COUNT
} Anm2Attribute; } Anm2Attribute;
static const char* ANM2_ATTRIBUTE_STRINGS[] = { static const char* ANM2_ATTRIBUTE_STRINGS[] =
{
#define X(name, str) str, #define X(name, str) str,
ANM2_ATTRIBUTE_LIST ANM2_ATTRIBUTE_LIST
#undef X #undef X
}; };
DEFINE_STRING_TO_ENUM_FUNCTION(ANM2_ATTRIBUTE_STRING_TO_ENUM, Anm2Attribute, ANM2_ATTRIBUTE_STRINGS, ANM2_ATTRIBUTE_COUNT) 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 enum Anm2Type
{ {
@@ -131,23 +135,33 @@ enum Anm2Type
struct Anm2Spritesheet struct Anm2Spritesheet
{ {
std::string path{}; std::string path{};
Texture texture;
std::vector<u8> pixels;
auto operator<=>(const Anm2Spritesheet&) const = default;
}; };
struct Anm2Layer struct Anm2Layer
{ {
std::string name = "New Layer"; std::string name = "New Layer";
s32 spritesheetID = ID_NONE; s32 spritesheetID{};
auto operator<=>(const Anm2Layer&) const = default;
}; };
struct Anm2Null struct Anm2Null
{ {
std::string name = "New Null"; std::string name = "New Null";
bool isShowRect = false; bool isShowRect = false;
auto operator<=>(const Anm2Null&) const = default;
}; };
struct Anm2Event struct Anm2Event
{ {
std::string name = "New Event"; std::string name = "New Event";
auto operator<=>(const Anm2Event&) const = default;
}; };
struct Anm2Frame struct Anm2Frame
@@ -163,8 +177,59 @@ struct Anm2Frame
vec2 position{}; vec2 position{};
vec2 size{}; vec2 size{};
vec2 scale = {100, 100}; vec2 scale = {100, 100};
vec3 offsetRGB{}; vec3 offsetRGB = COLOR_OFFSET_NONE;
vec4 tintRGBA = {1.0f, 1.0f, 1.0f, 1.0f}; vec4 tintRGBA = COLOR_OPAQUE;
auto operator<=>(const Anm2Frame&) const = default;
};
struct Anm2Item
{
bool isVisible = true;
std::vector<Anm2Frame> frames;
auto operator<=>(const Anm2Item&) const = default;
};
struct Anm2Animation
{
s32 frameNum = ANM2_FRAME_NUM_MIN;
std::string name = "New Animation";
bool isLoop = true;
Anm2Item rootAnimation;
std::unordered_map<s32, Anm2Item> layerAnimations;
std::vector<s32> layerOrder;
std::map<s32, Anm2Item> nullAnimations;
Anm2Item triggers;
auto operator<=>(const Anm2Animation&) const = default;
};
struct Anm2
{
std::string path{};
std::string createdBy = "robot";
std::string createdOn{};
std::map<s32, Anm2Spritesheet> spritesheets;
std::map<s32, Anm2Layer> layers;
std::map<s32, Anm2Null> nulls;
std::map<s32, Anm2Event> events;
std::map<s32, Anm2Animation> animations;
s32 defaultAnimationID = ID_NONE;
s32 fps = ANM2_FPS_DEFAULT;
s32 version{};
auto operator<=>(const Anm2&) const = default;
};
struct Anm2Reference
{
s32 animationID = ID_NONE;
Anm2Type itemType = ANM2_NONE;
s32 itemID = ID_NONE;
s32 frameIndex = INDEX_NONE;
f32 time = VALUE_NONE;
auto operator<=>(const Anm2Reference&) const = default;
}; };
struct Anm2FrameChange struct Anm2FrameChange
@@ -182,66 +247,6 @@ struct Anm2FrameChange
std::optional<vec4> tintRGBA; std::optional<vec4> tintRGBA;
}; };
struct Anm2Item
{
bool isVisible = true;
std::vector<Anm2Frame> frames;
};
struct Anm2Animation
{
s32 frameNum = ANM2_FRAME_NUM_MIN;
std::string name = "New Animation";
bool isLoop = true;
Anm2Item rootAnimation;
std::map<s32, Anm2Item> layerAnimations;
std::map<s32, Anm2Item> nullAnimations;
Anm2Item triggers;
};
struct Anm2
{
std::string path{};
std::string createdBy = "robot";
std::string createdOn{};
std::map<s32, Anm2Spritesheet> spritesheets;
std::map<s32, Anm2Layer> layers;
std::map<s32, Anm2Null> nulls;
std::map<s32, Anm2Event> events;
std::map<s32, Anm2Animation> animations;
std::map<s32, s32> layerMap; // index, id
s32 defaultAnimationID{};
s32 fps = ANM2_FPS_DEFAULT;
s32 version{};
};
struct Anm2Reference
{
s32 animationID = ID_NONE;
Anm2Type itemType = ANM2_NONE;
s32 itemID = ID_NONE;
s32 frameIndex = INDEX_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 enum Anm2MergeType
{ {
ANM2_MERGE_APPEND_FRAMES, ANM2_MERGE_APPEND_FRAMES,
@@ -257,31 +262,48 @@ enum Anm2ChangeType
ANM2_CHANGE_SET ANM2_CHANGE_SET
}; };
void anm2_layer_add(Anm2* self); enum OnionskinDrawOrder
void anm2_layer_remove(Anm2* self, s32 id); {
void anm2_null_add(Anm2* self); ONIONSKIN_BELOW,
void anm2_null_remove(Anm2* self, s32 id); ONIONSKIN_ABOVE
bool anm2_serialize(Anm2* self, const std::string& path); };
bool anm2_deserialize(Anm2* self, Resources* resources, const std::string& path);
void anm2_new(Anm2* self);
void anm2_created_on_set(Anm2* self);
s32 anm2_animation_add(Anm2* self);
void anm2_animation_remove(Anm2* self, s32 id);
void anm2_spritesheet_texture_load(Anm2* self, Resources* resources, const std::string& path, s32 id);
Anm2Animation* anm2_animation_from_reference(Anm2* self, Anm2Reference* reference); 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); Anm2Frame* anm2_frame_from_reference(Anm2* self, Anm2Reference* reference);
s32 anm2_frame_index_from_time(Anm2* self, Anm2Reference reference, f32 time); Anm2Item* anm2_item_from_reference(Anm2* self, Anm2Reference* reference);
Anm2Frame* anm2_frame_add(Anm2* self, Anm2Frame* frame, Anm2Reference* reference, s32 time = 0.0f); bool anm2_animation_deserialize_from_xml(Anm2Animation* animation, const std::string& xml);
void anm2_frame_erase(Anm2* self, Anm2Reference* reference); bool anm2_deserialize(Anm2* self, const std::string& path, bool isTextures = true);
void anm2_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time); bool anm2_frame_deserialize_from_xml(Anm2Frame* frame, const std::string& xml);
void anm2_reference_clear(Anm2Reference* self); bool anm2_serialize(Anm2* self, const std::string& path);
void anm2_reference_item_clear(Anm2Reference* self); s32 anm2_animation_add(Anm2* self, Anm2Animation* animation = nullptr, s32 id = ID_NONE);
void anm2_reference_frame_clear(Anm2Reference* self);
s32 anm2_animation_length_get(Anm2Animation* self); 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_length_set(Anm2Animation* self);
void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector<s32>& mergeIDs, Anm2MergeType type); void anm2_animation_merge(Anm2* self, s32 animationID, const std::vector<s32>& mergeIDs, Anm2MergeType type);
void anm2_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_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_frame_from_time(Anm2* self, Anm2Frame* frame, Anm2Reference reference, f32 time);
void anm2_scale(Anm2* self, f32 scale); 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_generate_from_grid(Anm2* self, Anm2Reference* reference, vec2 startPosition, vec2 size, vec2 pivot, s32 columns, s32 count, s32 delay);
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

@@ -1,39 +1,27 @@
#include "canvas.h" #include "canvas.h"
static void _canvas_texture_free(Canvas* self) static void _canvas_framebuffer_set(Canvas* self, const ivec2& size)
{ {
if (self->fbo != 0) glDeleteFramebuffers(1, &self->fbo);
if (self->rbo != 0) glDeleteRenderbuffers(1, &self->rbo);
if (self->texture != 0) glDeleteTextures(1, &self->texture);
}
static void _canvas_texture_init(Canvas* self, const vec2& size)
{
_canvas_texture_free(self);
self->size = size; self->size = size;
self->previousSize = size;
glGenFramebuffers(1, &self->fbo);
glBindFramebuffer(GL_FRAMEBUFFER, self->fbo); glBindFramebuffer(GL_FRAMEBUFFER, self->fbo);
glGenTextures(1, &self->texture); glBindTexture(GL_TEXTURE_2D, self->framebuffer);
glBindTexture(GL_TEXTURE_2D, self->texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self->size.x, self->size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (s32)self->size.x, (s32)self->size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self->texture, 0); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self->framebuffer, 0);
glGenRenderbuffers(1, &self->rbo);
glBindRenderbuffer(GL_RENDERBUFFER, self->rbo); glBindRenderbuffer(GL_RENDERBUFFER, self->rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, (s32)self->size.x, (s32)self->size.y); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, self->size.x, self->size.y);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, self->rbo); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, self->rbo);
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
} }
void canvas_init(Canvas* self, const vec2& size) void canvas_init(Canvas* self, const ivec2& size)
{ {
// Axis // Axis
glGenVertexArrays(1, &self->axisVAO); glGenVertexArrays(1, &self->axisVAO);
@@ -45,7 +33,7 @@ void canvas_init(Canvas* self, const vec2& size)
glBufferData(GL_ARRAY_BUFFER, sizeof(CANVAS_AXIS_VERTICES), CANVAS_AXIS_VERTICES, GL_STATIC_DRAW); glBufferData(GL_ARRAY_BUFFER, sizeof(CANVAS_AXIS_VERTICES), CANVAS_AXIS_VERTICES, GL_STATIC_DRAW);
glEnableVertexAttribArray(0); glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0); glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, sizeof(f32), (void*)0);
// Grid // Grid
glGenVertexArrays(1, &self->gridVAO); glGenVertexArrays(1, &self->gridVAO);
@@ -64,7 +52,7 @@ void canvas_init(Canvas* self, const vec2& size)
glBindVertexArray(self->rectVAO); glBindVertexArray(self->rectVAO);
glBindBuffer(GL_ARRAY_BUFFER, self->rectVBO); glBindBuffer(GL_ARRAY_BUFFER, self->rectVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(GL_VERTICES), GL_VERTICES, GL_STATIC_DRAW); glBufferData(GL_ARRAY_BUFFER, sizeof(CANVAS_RECT_VERTICES), CANVAS_RECT_VERTICES, GL_STATIC_DRAW);
glEnableVertexAttribArray(0); glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(f32), (void*)0);
@@ -103,14 +91,21 @@ void canvas_init(Canvas* self, const vec2& size)
glBindVertexArray(0); glBindVertexArray(0);
_canvas_texture_init(self, size); // Framebuffer
glGenTextures(1, &self->framebuffer);
glGenFramebuffers(1, &self->fbo);
glGenRenderbuffers(1, &self->rbo);
_canvas_framebuffer_set(self, size);
self->isInit = true;
} }
mat4 canvas_transform_get(Canvas* self, vec2 pan, f32 zoom, OriginType origin) mat4 canvas_transform_get(Canvas* self, vec2 pan, f32 zoom, OriginType origin)
{ {
f32 zoomFactor = PERCENT_TO_UNIT(zoom); f32 zoomFactor = PERCENT_TO_UNIT(zoom);
mat4 projection = glm::ortho(0.0f, self->size.x, 0.0f, self->size.y, -1.0f, 1.0f); mat4 projection = glm::ortho(0.0f, (f32)self->size.x, 0.0f, (f32)self->size.y, -1.0f, 1.0f);
mat4 view = mat4{1.0f}; mat4 view = mat4{1.0f};
vec2 size = vec2(self->size.x, self->size.y);
switch (origin) switch (origin)
{ {
@@ -118,7 +113,7 @@ mat4 canvas_transform_get(Canvas* self, vec2 pan, f32 zoom, OriginType origin)
view = glm::translate(view, vec3(pan, 0.0f)); view = glm::translate(view, vec3(pan, 0.0f));
break; break;
default: default:
view = glm::translate(view, vec3((self->size * 0.5f) + pan, 0.0f)); view = glm::translate(view, vec3((size * 0.5f) + pan, 0.0f));
break; break;
} }
@@ -138,15 +133,10 @@ void canvas_viewport_set(Canvas* self)
glViewport(0, 0, (s32)self->size.x, (s32)self->size.y); glViewport(0, 0, (s32)self->size.x, (s32)self->size.y);
} }
void canvas_texture_set(Canvas* self) void canvas_framebuffer_resize_check(Canvas* self)
{ {
static vec2 previousSize = {-1, -1}; if (self->previousSize != self->size)
_canvas_framebuffer_set(self, self->size);
if (previousSize != self->size)
{
_canvas_texture_init(self, self->size);
previousSize = self->size;
}
} }
void canvas_grid_draw(Canvas* self, GLuint& shader, mat4& transform, ivec2& size, ivec2& offset, vec4& color) void canvas_grid_draw(Canvas* self, GLuint& shader, mat4& transform, ivec2& size, ivec2& offset, vec4& color)
@@ -174,7 +164,7 @@ void canvas_texture_draw(Canvas* self, GLuint& shader, GLuint& texture, mat4& tr
glBindVertexArray(self->textureVAO); glBindVertexArray(self->textureVAO);
glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO); glBindBuffer(GL_ARRAY_BUFFER, self->textureVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(GL_UV_VERTICES), vertices, GL_DYNAMIC_DRAW); glBufferData(GL_ARRAY_BUFFER, sizeof(CANVAS_TEXTURE_VERTICES), vertices, GL_DYNAMIC_DRAW);
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture); glBindTexture(GL_TEXTURE_2D, texture);
@@ -207,13 +197,20 @@ void canvas_rect_draw(Canvas* self, const GLuint& shader, const mat4& transform,
glUseProgram(0); glUseProgram(0);
} }
void canvas_axes_draw(Canvas* self, GLuint& shader, mat4& transform, vec4& color) void canvas_axes_draw(Canvas* self, GLuint& shader, mat4& transform, vec4& color)
{ {
vec4 originNDC = transform * vec4(0.0f, 0.0f, 0.0f, 1.0f);
originNDC /= originNDC.w;
glUseProgram(shader); glUseProgram(shader);
glBindVertexArray(self->axisVAO); glBindVertexArray(self->axisVAO);
glUniformMatrix4fv(glGetUniformLocation(shader, SHADER_UNIFORM_TRANSFORM), 1, GL_FALSE, value_ptr(transform)); glUniform4fv(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), 1, value_ptr(color));
glUniform4f(glGetUniformLocation(shader, SHADER_UNIFORM_COLOR), color.r, color.g, color.b, color.a); glUniform2f(glGetUniformLocation(shader, SHADER_UNIFORM_ORIGIN_NDC), originNDC.x, originNDC.y);
glDrawArrays(GL_LINES, 0, 4); glUniform1i(glGetUniformLocation(shader, SHADER_UNIFORM_AXIS), 0);
glDrawArrays(GL_LINES, 0, 2);
glUniform1i(glGetUniformLocation(shader, SHADER_UNIFORM_AXIS), 1);
glDrawArrays(GL_LINES, 0, 2);
glBindVertexArray(0); glBindVertexArray(0);
glUseProgram(0); glUseProgram(0);
} }
@@ -230,30 +227,18 @@ void canvas_unbind(void)
void canvas_free(Canvas* self) void canvas_free(Canvas* self)
{ {
_canvas_texture_free(self); if (!self->isInit) return;
}
glDeleteFramebuffers(1, &self->fbo);
mat4 canvas_mvp_get(mat4& transform, vec2 size, vec2 position, vec2 pivot, f32 rotation, vec2 scale, vec2 pivotAlt, f32 rotationAlt) glDeleteRenderbuffers(1, &self->rbo);
{ glDeleteTextures(1, &self->framebuffer);
vec2 scaleAbsolute = glm::abs(scale); glDeleteVertexArrays(1, &self->axisVAO);
vec2 scaleSign = glm::sign(scale); glDeleteVertexArrays(1, &self->rectVAO);
f32 usedSign = (scaleSign.x * scaleSign.y) < 0.0f ? -1.0f : 1.0f; glDeleteVertexArrays(1, &self->gridVAO);
glDeleteVertexArrays(1, &self->textureVAO);
vec2 sizeScaled = size * scaleAbsolute; glDeleteBuffers(1, &self->axisVBO);
vec2 pivotScaled = pivot * scaleAbsolute; glDeleteBuffers(1, &self->rectVBO);
vec2 pivotAltScaled = pivotAlt * scaleAbsolute; glDeleteBuffers(1, &self->gridVBO);
glDeleteBuffers(1, &self->textureVBO);
vec2 pivotAltMirrored = pivotScaled + (pivotAltScaled - pivotScaled) * scaleSign; glDeleteBuffers(1, &self->textureEBO);
mat4 model = glm::translate(mat4(1.0f), vec3(position - pivotScaled, 0.0f));
model = glm::translate(model, vec3(pivotScaled, 0.0f));
model = glm::scale(model, vec3(scaleSign, 1.0f));
model = glm::rotate(model, glm::radians(rotation) * usedSign, vec3(0,0,1));
model = glm::translate(model, vec3(-pivotScaled, 0.0f));
model = glm::translate(model, vec3(pivotAltMirrored, 0.0f));
model = glm::rotate(model, glm::radians(rotationAlt) * usedSign, vec3(0,0,1));
model = glm::translate(model, vec3(-pivotAltMirrored, 0.0f));
model = glm::scale(model, vec3(sizeScaled, 1.0f));
return transform * model;
} }

View File

@@ -5,32 +5,39 @@
#define CANVAS_ZOOM_MIN 1.0f #define CANVAS_ZOOM_MIN 1.0f
#define CANVAS_ZOOM_MAX 2000.0f #define CANVAS_ZOOM_MAX 2000.0f
#define CANVAS_ZOOM_DEFAULT 100.0f #define CANVAS_ZOOM_DEFAULT 100.0f
#define CANVAS_ZOOM_STEP 10.0f #define CANVAS_ZOOM_STEP 100.0f
#define CANVAS_ZOOM_MOD 10.0f
#define CANVAS_GRID_MIN 1 #define CANVAS_GRID_MIN 1
#define CANVAS_GRID_MAX 1000 #define CANVAS_GRID_MAX 1000
#define CANVAS_GRID_DEFAULT 32 #define CANVAS_GRID_DEFAULT 32
#define CANVAS_LINE_LENGTH (FLT_MAX * 0.001f)
static const vec2 CANVAS_GRID_SIZE = {3200, 1600}; const inline vec2 CANVAS_PIVOT_SIZE = {4, 4};
static const vec2 CANVAS_PIVOT_SIZE = {8, 8}; const inline vec2 CANVAS_SCALE_DEFAULT = {1.0f, 1.0f};
static const vec2 CANVAS_SCALE_DEFAULT = {1.0f, 1.0f};
const f32 CANVAS_AXIS_VERTICES[] = const inline f32 CANVAS_AXIS_VERTICES[] = {-1.0f, 1.0f};
{
-CANVAS_LINE_LENGTH, 0.0f,
CANVAS_LINE_LENGTH, 0.0f,
0.0f, -CANVAS_LINE_LENGTH,
0.0f, CANVAS_LINE_LENGTH
};
const f32 CANVAS_GRID_VERTICES[] = const inline f32 CANVAS_GRID_VERTICES[] =
{ {
-1.0f, -1.0f, -1.0f, -1.0f,
3.0f, -1.0f, 3.0f, -1.0f,
-1.0f, 3.0f -1.0f, 3.0f
}; };
const inline f32 CANVAS_RECT_VERTICES[] =
{
0, 0,
1, 0,
1, 1,
0, 1
};
const inline f32 CANVAS_TEXTURE_VERTICES[] =
{
0, 0, 0.0f, 0.0f,
1, 0, 1.0f, 0.0f,
1, 1, 1.0f, 1.0f,
0, 1, 0.0f, 1.0f
};
struct Canvas struct Canvas
{ {
GLuint fbo{}; GLuint fbo{};
@@ -41,37 +48,40 @@ struct Canvas
GLuint rectVBO{}; GLuint rectVBO{};
GLuint gridVAO{}; GLuint gridVAO{};
GLuint gridVBO{}; GLuint gridVBO{};
GLuint texture{}; GLuint framebuffer{};
GLuint textureEBO{};
GLuint textureVAO{}; GLuint textureVAO{};
GLuint textureVBO{}; GLuint textureVBO{};
vec2 size{}; GLuint textureEBO{};
ivec2 size{};
ivec2 previousSize{};
bool isInit = false;
}; };
void canvas_init(Canvas* self, const vec2& size); #define UV_VERTICES(uvMin, uvMax) \
mat4 canvas_transform_get(Canvas* self, vec2 pan, f32 zoom, OriginType origin); { \
void canvas_clear(vec4& color); 0, 0, uvMin.x, uvMin.y, \
void canvas_bind(Canvas* self); 1, 0, uvMax.x, uvMin.y, \
void canvas_viewport_set(Canvas* self); 1, 1, uvMax.x, uvMax.y, \
void canvas_unbind(void); 0, 1, uvMin.x, uvMax.y \
void canvas_texture_set(Canvas* self); }
void canvas_grid_draw(Canvas* self, GLuint& shader, mat4& transform, ivec2& size, ivec2& offset, vec4& color);
void canvas_axes_draw(Canvas* self, GLuint& shader, mat4& transform, vec4& color);
void canvas_rect_draw(Canvas* self, const GLuint& shader, const mat4& transform, const vec4& color);
void canvas_free(Canvas* self);
void canvas_draw(Canvas* self);
mat4 canvas_mvp_get #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)
mat4& transform, #define ATLAS_UV_ARGS(type) ATLAS_UV_MIN(type), ATLAS_UV_MAX(type)
vec2 size, #define ATLAS_UV_VERTICES(type) UV_VERTICES(ATLAS_UV_MIN(type), ATLAS_UV_MAX(type))
vec2 position = {0.0f, 0.0f},
vec2 pivot = {0.0f, 0.0f}, mat4 canvas_transform_get(Canvas* self, vec2 pan, f32 zoom, OriginType origin);
f32 rotation = {0.0f}, void canvas_axes_draw(Canvas* self, GLuint& shader, mat4& transform, vec4& color);
vec2 scale = {1.0f, 1.0f}, void canvas_bind(Canvas* self);
vec2 pivotAlt = {0.0f, 0.0f}, void canvas_clear(vec4& color);
f32 rotationAlt = {0.0f} void canvas_draw(Canvas* self);
); void canvas_free(Canvas* self);
void canvas_grid_draw(Canvas* self, GLuint& shader, mat4& transform, ivec2& size, ivec2& offset, vec4& color);
void canvas_init(Canvas* self, const ivec2& size);
void canvas_rect_draw(Canvas* self, const GLuint& shader, const mat4& transform, const vec4& color);
void canvas_framebuffer_resize_check(Canvas* self);
void canvas_unbind(void);
void canvas_viewport_set(Canvas* self);
void canvas_texture_draw void canvas_texture_draw
( (
@@ -79,7 +89,7 @@ void canvas_texture_draw
GLuint& shader, GLuint& shader,
GLuint& texture, GLuint& texture,
mat4& transform, mat4& transform,
const f32* vertices = GL_UV_VERTICES, const f32* vertices = CANVAS_TEXTURE_VERTICES,
vec4 tint = COLOR_OPAQUE, vec4 tint = COLOR_OPAQUE,
vec3 colorOffset = COLOR_OFFSET_NONE vec3 colorOffset = COLOR_OFFSET_NONE
); );

View File

@@ -1,97 +1,105 @@
#include "clipboard.h" #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) 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) void clipboard_cut(Clipboard* self)
{ {
self->item = self->hoveredItem; clipboard_copy(self);
_clipboard_item_remove(&self->item, self->anm2);
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) void clipboard_init(Clipboard* self, Anm2* anm2)
@@ -99,3 +107,7 @@ void clipboard_init(Clipboard* self, Anm2* anm2)
self->anm2 = anm2; self->anm2 = anm2;
} }
bool clipboard_is_value(void)
{
return SDL_HasClipboardText();
}

View File

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

View File

@@ -30,55 +30,54 @@ void dialog_init(Dialog* self, SDL_Window* window)
void dialog_anm2_open(Dialog* self) void dialog_anm2_open(Dialog* self)
{ {
SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_ANM2, 1, nullptr, false); SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_ANM2, std::size(DIALOG_FILE_FILTER_ANM2), nullptr, false);
self->type = DIALOG_ANM2_OPEN; self->type = DIALOG_ANM2_OPEN;
} }
void dialog_anm2_save(Dialog* self) void dialog_anm2_save(Dialog* self)
{ {
SDL_ShowSaveFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_ANM2, 1, nullptr); SDL_ShowSaveFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_ANM2, std::size(DIALOG_FILE_FILTER_ANM2), nullptr);
self->type = DIALOG_ANM2_SAVE; self->type = DIALOG_ANM2_SAVE;
} }
void dialog_spritesheet_add(Dialog* self) void dialog_spritesheet_add(Dialog* self)
{ {
SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_PNG, 1, nullptr, false); SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_PNG, std::size(DIALOG_FILE_FILTER_PNG), nullptr, false);
self->type = DIALOG_SPRITESHEET_ADD; self->type = DIALOG_SPRITESHEET_ADD;
} }
void dialog_spritesheet_replace(Dialog* self, s32 id) void dialog_spritesheet_replace(Dialog* self, s32 id)
{ {
SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_PNG, 1, nullptr, false); SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_PNG, std::size(DIALOG_FILE_FILTER_PNG), nullptr, false);
self->replaceID = id; self->replaceID = id;
self->type = DIALOG_SPRITESHEET_REPLACE; self->type = DIALOG_SPRITESHEET_REPLACE;
} }
void dialog_render_path_set(Dialog* self) void dialog_render_path_set(Dialog* self, RenderType type)
{ {
SDL_ShowSaveFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_RENDER, 2, nullptr); SDL_DialogFileFilter filter = DIALOG_RENDER_FILE_FILTERS[type];
self->type = DIALOG_RENDER_PATH_SET;
}
void dialog_render_directory_set(Dialog* self) if (type == RENDER_PNG)
{
SDL_ShowOpenFolderDialog(_dialog_callback, self, self->window, nullptr, false); SDL_ShowOpenFolderDialog(_dialog_callback, self, self->window, nullptr, false);
self->type = DIALOG_RENDER_DIRECTORY_SET; else
SDL_ShowSaveFileDialog(_dialog_callback, self, self->window, &filter, 1, nullptr);
self->type = DIALOG_RENDER_PATH_SET;
} }
void dialog_ffmpeg_path_set(Dialog* self) void dialog_ffmpeg_path_set(Dialog* self)
{ {
SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_FFMPEG, 1, nullptr, false); SDL_ShowOpenFileDialog(_dialog_callback, self, self->window, DIALOG_FILE_FILTER_FFMPEG, std::size(DIALOG_FILE_FILTER_FFMPEG), nullptr, false);
self->type = DIALOG_FFMPEG_PATH_SET; self->type = DIALOG_FFMPEG_PATH_SET;
} }
void dialog_explorer_open(const std::string& path) void dialog_explorer_open(const std::string& path)
{ {
#ifdef _WIN32 #ifdef _WIN32
ShellExecuteA(NULL, "open", path.c_str(), NULL, NULL, SW_SHOWNORMAL); ShellExecuteA(NULL, DIALOG_FILE_EXPLORER_COMMAND, path.c_str(), NULL, NULL, SW_SHOWNORMAL);
#else #else
char cmd[512]; char command[DIALOG_FILE_EXPLORER_COMMAND_SIZE];
snprintf(cmd, sizeof(cmd), "xdg-open \"%s\" &", path.c_str()); snprintf(command, sizeof(command), DIALOG_FILE_EXPLORER_COMMAND, path.c_str());
system(cmd); system(command);
#endif #endif
} }

View File

@@ -1,7 +1,16 @@
#pragma once #pragma once
#include "render.h"
#include "window.h" #include "window.h"
#define DIALOG_FILE_EXPLORER_COMMAND_SIZE 512
#ifdef _WIN32
#define DIALOG_FILE_EXPLORER_COMMAND "open"
#else
#define DIALOG_FILE_EXPLORER_COMMAND "xdg-open \"%s\" &"
#endif
const SDL_DialogFileFilter DIALOG_FILE_FILTER_ANM2[] = const SDL_DialogFileFilter DIALOG_FILE_FILTER_ANM2[] =
{ {
{"Anm2 file", "anm2;xml"} {"Anm2 file", "anm2;xml"}
@@ -12,15 +21,21 @@ const SDL_DialogFileFilter DIALOG_FILE_FILTER_PNG[] =
{"PNG image", "png"} {"PNG image", "png"}
}; };
const SDL_DialogFileFilter DIALOG_FILE_FILTER_RENDER[] = const SDL_DialogFileFilter DIALOG_RENDER_FILE_FILTERS[] =
{ {
{"PNG image", "png"},
{"GIF image", "gif"}, {"GIF image", "gif"},
{"WebM video", "webm"} {"WebM video", "webm"},
{"MP4 video", "mp4"}
}; };
const SDL_DialogFileFilter DIALOG_FILE_FILTER_FFMPEG[] = const SDL_DialogFileFilter DIALOG_FILE_FILTER_FFMPEG[] =
{ {
#ifdef _WIN32
{"Executable", "exe"}
#else
{"Executable", ""} {"Executable", ""}
#endif
}; };
enum DialogType enum DialogType
@@ -31,7 +46,6 @@ enum DialogType
DIALOG_SPRITESHEET_ADD, DIALOG_SPRITESHEET_ADD,
DIALOG_SPRITESHEET_REPLACE, DIALOG_SPRITESHEET_REPLACE,
DIALOG_RENDER_PATH_SET, DIALOG_RENDER_PATH_SET,
DIALOG_RENDER_DIRECTORY_SET,
DIALOG_FFMPEG_PATH_SET DIALOG_FFMPEG_PATH_SET
}; };
@@ -50,7 +64,7 @@ void dialog_anm2_open(Dialog* self);
void dialog_spritesheet_add(Dialog* self); void dialog_spritesheet_add(Dialog* self);
void dialog_spritesheet_replace(Dialog* self, s32 id); void dialog_spritesheet_replace(Dialog* self, s32 id);
void dialog_anm2_save(Dialog* self); void dialog_anm2_save(Dialog* self);
void dialog_render_path_set(Dialog* self); void dialog_render_path_set(Dialog* self, RenderType type);
void dialog_render_directory_set(Dialog* self); void dialog_render_directory_set(Dialog* self);
void dialog_ffmpeg_path_set(Dialog* self); void dialog_ffmpeg_path_set(Dialog* self);
void dialog_reset(Dialog* self); void dialog_reset(Dialog* self);

View File

@@ -20,31 +20,32 @@ void editor_draw(Editor* self)
GLuint& shaderGrid = self->resources->shaders[SHADER_GRID]; GLuint& shaderGrid = self->resources->shaders[SHADER_GRID];
mat4 transform = canvas_transform_get(&self->canvas, self->settings->editorPan, self->settings->editorZoom, ORIGIN_TOP_LEFT); mat4 transform = canvas_transform_get(&self->canvas, self->settings->editorPan, self->settings->editorZoom, ORIGIN_TOP_LEFT);
canvas_texture_set(&self->canvas); canvas_framebuffer_resize_check(&self->canvas);
canvas_bind(&self->canvas); canvas_bind(&self->canvas);
canvas_viewport_set(&self->canvas); canvas_viewport_set(&self->canvas);
canvas_clear(self->settings->editorBackgroundColor); canvas_clear(self->settings->editorBackgroundColor);
if (self->spritesheetID != ID_NONE) if (Anm2Spritesheet* spritesheet = map_find(self->anm2->spritesheets, self->spritesheetID))
{ {
Texture texture = self->resources->textures[self->spritesheetID]; Texture& texture = spritesheet->texture;
mat4 mvp = canvas_mvp_get(transform, texture.size);
canvas_texture_draw(&self->canvas, shaderTexture, texture.id, mvp); mat4 spritesheetTransform = transform * quad_model_get(texture.size);
canvas_texture_draw(&self->canvas, shaderTexture, texture.id, spritesheetTransform);
if (self->settings->editorIsBorder) if (self->settings->editorIsBorder)
canvas_rect_draw(&self->canvas, shaderLine, mvp, EDITOR_BORDER_COLOR); canvas_rect_draw(&self->canvas, shaderLine, spritesheetTransform, EDITOR_BORDER_COLOR);
Anm2Frame* frame = (Anm2Frame*)anm2_frame_from_reference(self->anm2, self->reference); Anm2Frame* frame = (Anm2Frame*)anm2_frame_from_reference(self->anm2, self->reference);
if (frame) if (frame)
{ {
mvp = canvas_mvp_get(transform, frame->size, frame->crop); mat4 cropTransform = transform * quad_model_get(frame->size, frame->crop);
canvas_rect_draw(&self->canvas, shaderLine, mvp, EDITOR_FRAME_COLOR); canvas_rect_draw(&self->canvas, shaderLine, cropTransform, EDITOR_FRAME_COLOR);
mvp = canvas_mvp_get(transform, 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); f32 vertices[] = ATLAS_UV_VERTICES(ATLAS_PIVOT);
canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, mvp, vertices, EDITOR_PIVOT_COLOR); canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, pivotTransform, vertices, EDITOR_PIVOT_COLOR);
} }
} }

View File

@@ -23,15 +23,24 @@ ffmpeg_render
case RENDER_WEBM: case RENDER_WEBM:
command = std::format(FFMPEG_WEBM_FORMAT, ffmpegPath, size.x, size.y, fps, outputPath); command = std::format(FFMPEG_WEBM_FORMAT, ffmpegPath, size.x, size.y, fps, outputPath);
break; break;
case RENDER_MP4:
command = std::format(FFMPEG_MP4_FORMAT, ffmpegPath, size.x, size.y, fps, outputPath);
break;
default: default:
return false; break;
} }
#if _WIN32
command = string_quote(command);
#endif
log_command(command);
FILE* fp = POPEN(command.c_str(), PWRITE_MODE); FILE* fp = POPEN(command.c_str(), PWRITE_MODE);
if (!fp) if (!fp)
{ {
log_info(std::format(FFMPEG_POPEN_ERROR, strerror(errno))); log_error(std::format(FFMPEG_POPEN_ERROR, strerror(errno)));
return false; return false;
} }

View File

@@ -19,6 +19,12 @@ static constexpr const char* FFMPEG_WEBM_FORMAT =
"-c:v libvpx-vp9 -crf 30 -b:v 0 -pix_fmt yuva420p -row-mt 1 -threads 0 -speed 2 " "-c:v libvpx-vp9 -crf 30 -b:v 0 -pix_fmt yuva420p -row-mt 1 -threads 0 -speed 2 "
"-auto-alt-ref 0 -an \"{4}\""; "-auto-alt-ref 0 -an \"{4}\"";
static constexpr const char* FFMPEG_MP4_FORMAT =
"\"{0}\" -y "
"-f rawvideo -pix_fmt rgba -s {1}x{2} -r {3} -i pipe:0 "
"-vf \"format=yuv420p,scale=trunc(iw/2)*2:trunc(ih/2)*2\" "
"-c:v libx265 -crf 20 -preset slow "
"-tag:v hvc1 -movflags +faststart -an \"{4}\"";
bool bool
ffmpeg_render ffmpeg_render

View File

@@ -26,23 +26,26 @@ void generate_preview_draw(GeneratePreview* self)
canvas_clear(self->settings->previewBackgroundColor); canvas_clear(self->settings->previewBackgroundColor);
Anm2Item* item = anm2_item_from_reference(self->anm2, self->reference); Anm2Item* item = anm2_item_from_reference(self->anm2, self->reference);
Texture* texture = map_find(self->resources->textures, self->anm2->layers[self->reference->itemID].spritesheetID);
if (item && texture && !texture->isInvalid) if (item)
{ {
if (Anm2Spritesheet* spritesheet = map_find(self->anm2->spritesheets, self->anm2->layers[self->reference->itemID].spritesheetID))
{
Texture& texture = spritesheet->texture;
const s32 index = std::clamp((s32)(self->time * count), 0, count); const s32 index = std::clamp((s32)(self->time * count), 0, count);
const s32 row = index / columns; const s32 row = index / columns;
const s32 column = index % columns; const s32 column = index % columns;
vec2 crop = startPosition + vec2(size.x * column, size.y * row); vec2 crop = startPosition + vec2(size.x * column, size.y * row);
vec2 uvMin = crop / vec2(texture->size); vec2 textureSize = vec2(texture.size);
vec2 uvMax = (crop + size) / vec2(texture->size); vec2 uvMin = (crop + vec2(0.5f)) / textureSize;
vec2 uvMax = (crop + size - vec2(0.5f)) / textureSize;
f32 vertices[] = UV_VERTICES(uvMin, uvMax); f32 vertices[] = UV_VERTICES(uvMin, uvMax);
mat4 model = quad_model_get(size, {}, pivot, {}, CANVAS_SCALE_DEFAULT); mat4 generateTransform = transform * quad_model_get(size, {}, pivot);
mat4 generateTransform = transform * model; canvas_texture_draw(&self->canvas, shaderTexture, texture.id, generateTransform, vertices, COLOR_OPAQUE, COLOR_OFFSET_NONE);
}
canvas_texture_draw(&self->canvas, shaderTexture, texture->id, generateTransform, vertices, COLOR_OPAQUE, COLOR_OFFSET_NONE);
} }
canvas_unbind(); canvas_unbind();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

56
src/log.cpp Normal file
View File

@@ -0,0 +1,56 @@
#include "log.h"
inline std::ofstream logFile;
std::string log_path_get(void)
{
return preferences_path_get() + LOG_PATH;
}
void log_write(const std::string& string)
{
std::println("{}", string);
if (logFile.is_open())
{
logFile << string << '\n';
logFile.flush();
}
}
void log_init(void)
{
std::string logFilepath = log_path_get();
logFile.open(logFilepath, std::ios::out | std::ios::trunc);
if (!logFile) std::println("{}", std::format(LOG_INIT_ERROR, logFilepath));
}
void log_error(const std::string& error)
{
log_write(LOG_ERROR_FORMAT + error);
}
void log_info(const std::string& info)
{
log_write(LOG_INFO_FORMAT + info);
}
void log_warning(const std::string& warning)
{
log_write(LOG_WARNING_FORMAT + warning);
}
void log_imgui(const std::string& imgui)
{
log_write(LOG_IMGUI_FORMAT + imgui);
}
void log_command(const std::string& command)
{
log_write(LOG_COMMAND_FORMAT + command);
}
void log_free(void)
{
logFile.close();
}

21
src/log.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include "COMMON.h"
#define LOG_WARNING_FORMAT "[WARNING] "
#define LOG_ERROR_FORMAT "[ERROR] "
#define LOG_INFO_FORMAT "[INFO] "
#define LOG_IMGUI_FORMAT "[IMGUI] "
#define LOG_INIT_ERROR "[ERROR] Failed to open log file: {}"
#define LOG_COMMAND_FORMAT "[COMMAND] "
#define LOG_PATH "log.txt"
std::string log_path_get(void);
void log_init(void);
void log_write(const std::string& file);
void log_error(const std::string& error);
void log_info(const std::string& info);
void log_warning(const std::string& warning);
void log_imgui(const std::string& imgui);
void log_command(const std::string& command);
void log_free(void);

View File

@@ -4,7 +4,7 @@ static bool _anm2_rescale(const std::string& file, f32 scale)
{ {
Anm2 anm2; Anm2 anm2;
if (!anm2_deserialize(&anm2, nullptr, file)) return false; if (!anm2_deserialize(&anm2, file, false)) return false;
anm2_scale(&anm2, scale); anm2_scale(&anm2, scale);
return anm2_serialize(&anm2, file); return anm2_serialize(&anm2, file);
} }
@@ -14,6 +14,8 @@ main(s32 argc, char* argv[])
{ {
State state; State state;
log_init();
if (argc > 0 && argv[1]) if (argc > 0 && argv[1])
{ {
if (std::string(argv[1]) == ARGUMENT_RESCALE) if (std::string(argv[1]) == ARGUMENT_RESCALE)
@@ -33,9 +35,19 @@ main(s32 argc, char* argv[])
return EXIT_FAILURE; 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 else
if (argv[1]) if (argv[1]) state.argument = argv[1];
state.argument = argv[1];
} }
init(&state); init(&state);

View File

@@ -1,5 +1,10 @@
#pragma once #pragma once
#include <SDL3/SDL_main.h>
#define ARGUMENT_TEST "--test"
#define ARGUMENT_TEST_GL "--test-gl"
#define ARGUMENT_RESCALE "--rescale" #define ARGUMENT_RESCALE "--rescale"
#define ARGUMENT_RESCALE_ARGUMENT_ERROR "--rescale: specify both anm2 and scale arguments" #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." #define ARGUMENT_RESCALE_ANM2_ERROR "Unable to rescale anm2 {} by value {}. Make sure the file is valid."

View File

@@ -29,7 +29,7 @@ void preview_tick(Preview* self)
{ {
if (self->isRender) if (self->isRender)
{ {
vec2& size = self->canvas.size; ivec2& size = self->canvas.size;
u32 framebufferPixelCount = size.x * size.y * TEXTURE_CHANNELS; u32 framebufferPixelCount = size.x * size.y * TEXTURE_CHANNELS;
std::vector<u8> framebufferPixels(framebufferPixelCount); std::vector<u8> framebufferPixels(framebufferPixelCount);
Texture frameTexture; Texture frameTexture;
@@ -40,11 +40,11 @@ void preview_tick(Preview* self)
glReadPixels(0, 0, size.x, size.y, GL_RGBA, GL_UNSIGNED_BYTE, framebufferPixels.data()); glReadPixels(0, 0, size.x, size.y, GL_RGBA, GL_UNSIGNED_BYTE, framebufferPixels.data());
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); 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); self->renderFrames.push_back(frameTexture);
} }
time += (f32)self->anm2->fps / TICK_RATE; time += (f32)self->anm2->fps / TICK_DELAY;
if (time >= (f32)animation->frameNum - 1) if (time >= (f32)animation->frameNum - 1)
{ {
@@ -72,10 +72,7 @@ void preview_tick(Preview* self)
time = std::clamp(time, 0.0f, std::max(0.0f, (f32)animation->frameNum - 1)); time = std::clamp(time, 0.0f, std::max(0.0f, (f32)animation->frameNum - 1));
else else
time = std::max(time, 0.0f); time = std::max(time, 0.0f);
} }
} }
void preview_draw(Preview* self) void preview_draw(Preview* self)
@@ -84,11 +81,12 @@ void preview_draw(Preview* self)
ivec2& gridOffset = self->settings->previewGridOffset; ivec2& gridOffset = self->settings->previewGridOffset;
vec4& gridColor = self->settings->previewGridColor; vec4& gridColor = self->settings->previewGridColor;
GLuint& shaderLine = self->resources->shaders[SHADER_LINE]; GLuint& shaderLine = self->resources->shaders[SHADER_LINE];
GLuint& shaderAxis = self->resources->shaders[SHADER_AXIS];
GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE]; GLuint& shaderTexture = self->resources->shaders[SHADER_TEXTURE];
GLuint& shaderGrid = self->resources->shaders[SHADER_GRID]; GLuint& shaderGrid = self->resources->shaders[SHADER_GRID];
mat4 transform = canvas_transform_get(&self->canvas, self->settings->previewPan, self->settings->previewZoom, ORIGIN_CENTER); mat4 transform = canvas_transform_get(&self->canvas, self->settings->previewPan, self->settings->previewZoom, ORIGIN_CENTER);
canvas_texture_set(&self->canvas); canvas_framebuffer_resize_check(&self->canvas);
canvas_bind(&self->canvas); canvas_bind(&self->canvas);
canvas_viewport_set(&self->canvas); canvas_viewport_set(&self->canvas);
@@ -98,94 +96,85 @@ void preview_draw(Preview* self)
canvas_grid_draw(&self->canvas, shaderGrid, transform, gridSize, gridOffset, gridColor); canvas_grid_draw(&self->canvas, shaderGrid, transform, gridSize, gridOffset, gridColor);
if (self->settings->previewIsAxes) if (self->settings->previewIsAxes)
canvas_axes_draw(&self->canvas, shaderLine, transform, self->settings->previewAxesColor); canvas_axes_draw(&self->canvas, shaderAxis, transform, self->settings->previewAxesColor);
Anm2Animation* animation = anm2_animation_from_reference(self->anm2, self->reference); auto animation_draw = [&](s32 animationID)
s32& animationID = self->reference->animationID;
if (animation)
{ {
Anm2Frame root; Anm2Animation* animation = map_find(self->anm2->animations, animationID);
mat4 rootModel = mat4(1.0f); if (!animation) return;
anm2_frame_from_time(self->anm2, &root, Anm2Reference{animationID, ANM2_ROOT}, self->time); auto root_draw = [&](Anm2Frame root, vec3 colorOffset = {}, f32 alphaOffset = {}, bool isOnionskin = {})
if (self->settings->previewIsRootTransform)
rootModel = quad_parent_model_get(root.position, vec2(0.0f), root.rotation, PERCENT_TO_UNIT(root.scale));
// Root
if (self->settings->previewIsTargets && animation->rootAnimation.isVisible && root.isVisible)
{ {
mat4 model = quad_model_get(PREVIEW_TARGET_SIZE, root.position, PREVIEW_TARGET_SIZE * 0.5f, root.rotation, PERCENT_TO_UNIT(root.scale)); mat4 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; mat4 rootTransform = transform * model;
f32 vertices[] = ATLAS_UV_VERTICES(ATLAS_TARGET); vec4 color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : PREVIEW_ROOT_COLOR;
canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, rootTransform, vertices, PREVIEW_ROOT_COLOR); AtlasType atlas = self->settings->previewIsAltIcons ? ATLAS_TARGET_ALT : ATLAS_TARGET;
} f32 vertices[] = ATLAS_UV_VERTICES(atlas);
canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, rootTransform, vertices, color);
};
// Layers auto layer_draw = [&](mat4 rootModel, s32 id, f32 time, vec3 colorOffset = {}, f32 alphaOffset = {}, bool isOnionskin = {})
for (auto [i, id] : self->anm2->layerMap)
{ {
Anm2Frame frame;
Anm2Item& layerAnimation = animation->layerAnimations[id]; Anm2Item& layerAnimation = animation->layerAnimations[id];
if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0) return;
if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0) Anm2Frame frame;
continue; anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_LAYER, id}, time);
if (!frame.isVisible) return;
anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_LAYER, id}, self->time); mat4 model = quad_model_get(frame.size, frame.position, frame.pivot, PERCENT_TO_UNIT(frame.scale), frame.rotation);
if (!frame.isVisible)
continue;
mat4 model = quad_model_get(frame.size, frame.position, frame.pivot, frame.rotation, PERCENT_TO_UNIT(frame.scale));
mat4 layerTransform = transform * (rootModel * model); mat4 layerTransform = transform * (rootModel * model);
vec3 frameColorOffset = frame.offsetRGB + colorOffset;
vec4 frameTint = frame.tintRGBA;
frameTint.a = std::max(0.0f, frameTint.a - alphaOffset);
Texture* texture = map_find(self->resources->textures, self->anm2->layers[id].spritesheetID); Anm2Spritesheet* spritesheet = map_find(self->anm2->spritesheets, self->anm2->layers[id].spritesheetID);
if (!spritesheet) return;
if (texture && !texture->isInvalid) 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); f32 vertices[] = UV_VERTICES(uvMin, uvMax);
canvas_texture_draw(&self->canvas, shaderTexture, texture.id, layerTransform, vertices, frameTint, frameColorOffset);
canvas_texture_draw(&self->canvas, shaderTexture, texture->id, layerTransform, vertices, frame.tintRGBA, frame.offsetRGB);
}
if (self->settings->previewIsBorder) if (self->settings->previewIsBorder)
canvas_rect_draw(&self->canvas, shaderLine, layerTransform, PREVIEW_BORDER_COLOR); {
vec4 borderColor = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : PREVIEW_BORDER_COLOR;
canvas_rect_draw(&self->canvas, shaderLine, layerTransform, borderColor);
}
if (self->settings->previewIsPivots) if (self->settings->previewIsPivots)
{ {
vec4 pivotColor = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : PREVIEW_PIVOT_COLOR;
f32 vertices[] = ATLAS_UV_VERTICES(ATLAS_PIVOT); f32 vertices[] = ATLAS_UV_VERTICES(ATLAS_PIVOT);
mat4 pivotModel = quad_model_get(CANVAS_PIVOT_SIZE, frame.position, CANVAS_PIVOT_SIZE * 0.5f, frame.rotation, PERCENT_TO_UNIT(frame.scale)); mat4 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); mat4 pivotTransform = transform * (rootModel * pivotModel);
canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, pivotTransform, vertices, PREVIEW_PIVOT_COLOR); canvas_texture_draw(&self->canvas, shaderTexture, self->resources->atlas.id, pivotTransform, vertices, pivotColor);
}
} }
};
// Nulls auto null_draw = [&](mat4 rootModel, s32 id, f32 time, vec3 colorOffset = {}, f32 alphaOffset = {}, bool isOnionskin = {})
if (self->settings->previewIsTargets)
{ {
for (auto& [id, nullAnimation] : animation->nullAnimations) Anm2Item& nullAnimation = animation->nullAnimations[id];
{ if (!nullAnimation.isVisible || nullAnimation.frames.size() <= 0) return;
if (!nullAnimation.isVisible || nullAnimation.frames.size() <= 0)
continue;
Anm2Frame frame; Anm2Frame frame;
anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_NULL, id}, self->time); anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationID, ANM2_NULL, id}, time);
if (!frame.isVisible) return;
if (!frame.isVisible)
continue;
Anm2Null null = self->anm2->nulls[id]; Anm2Null null = self->anm2->nulls[id];
vec4 color = (self->reference->itemType == ANM2_NULL && self->reference->itemID == id) ? vec4 color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) :
PREVIEW_NULL_SELECTED_COLOR : (self->reference->itemType == ANM2_NULL && self->reference->itemID == id) ?
PREVIEW_NULL_COLOR; PREVIEW_NULL_SELECTED_COLOR : PREVIEW_NULL_COLOR;
vec2 size = null.isShowRect ? CANVAS_PIVOT_SIZE : PREVIEW_TARGET_SIZE; vec2 size = null.isShowRect ? CANVAS_PIVOT_SIZE : PREVIEW_TARGET_SIZE;
AtlasType atlas = null.isShowRect ? ATLAS_SQUARE : ATLAS_TARGET; AtlasType atlas = null.isShowRect ? ATLAS_SQUARE : self->settings->previewIsAltIcons ? ATLAS_TARGET_ALT : ATLAS_TARGET;
mat4 model = quad_model_get(size, frame.position, size * 0.5f, frame.rotation, PERCENT_TO_UNIT(frame.scale)); mat4 model = quad_model_get(size, frame.position, size * 0.5f, PERCENT_TO_UNIT(frame.scale), frame.rotation);
mat4 nullTransform = transform * (rootModel * model); mat4 nullTransform = transform * (rootModel * model);
f32 vertices[] = ATLAS_UV_VERTICES(atlas); f32 vertices[] = ATLAS_UV_VERTICES(atlas);
@@ -194,58 +183,55 @@ void preview_draw(Preview* self)
if (null.isShowRect) if (null.isShowRect)
{ {
mat4 rectModel = quad_model_get(PREVIEW_NULL_RECT_SIZE, frame.position, PREVIEW_NULL_RECT_SIZE * 0.5f, frame.rotation, PERCENT_TO_UNIT(frame.scale)); 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); mat4 rectTransform = transform * (rootModel * rectModel);
canvas_rect_draw(&self->canvas, shaderLine, rectTransform, color); canvas_rect_draw(&self->canvas, shaderLine, rectTransform, color);
} }
} };
}
}
s32& animationOverlayID = self->animationOverlayID; auto base_draw = [&](f32 time, vec3 colorOffset = {}, f32 alphaOffset = {}, bool isOnionskin = {})
Anm2Animation* animationOverlay = map_find(self->anm2->animations, animationOverlayID);
if (animationOverlay)
{ {
Anm2Frame root; Anm2Frame root;
mat4 rootModel = mat4(1.0f); anm2_frame_from_time(self->anm2, &root, Anm2Reference{animationID, ANM2_ROOT}, time);
anm2_frame_from_time(self->anm2, &root, Anm2Reference{animationOverlayID, ANM2_ROOT}, self->time); mat4 rootModel = self->settings->previewIsRootTransform ?
quad_model_parent_get(root.position, {}, PERCENT_TO_UNIT(root.scale), root.rotation) : mat4(1.0f);
if (self->settings->previewIsRootTransform) if (self->settings->previewIsIcons && animation->rootAnimation.isVisible && root.isVisible)
rootModel = quad_parent_model_get(root.position, vec2(0.0f), root.rotation, PERCENT_TO_UNIT(root.scale)); 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->previewIsIcons)
for (auto& [id, _] : animation->nullAnimations)
null_draw(rootModel, id, time, colorOffset, alphaOffset, isOnionskin);
};
auto onionskin_draw = [&](s32 count, s32 direction, vec3 colorOffset)
{ {
Anm2Frame frame; for (s32 i = 1; i <= count; i++)
Anm2Item& layerAnimation = animationOverlay->layerAnimations[id]; {
f32 time = self->time + (f32)(direction * i);
if (!layerAnimation.isVisible || layerAnimation.frames.size() <= 0) f32 alphaOffset = (1.0f / (count + 1)) * i;
continue; base_draw(time, colorOffset, alphaOffset, true);
anm2_frame_from_time(self->anm2, &frame, Anm2Reference{animationOverlayID, ANM2_LAYER, id}, self->time);
if (!frame.isVisible)
continue;
Texture* texture = map_find(self->resources->textures, self->anm2->layers[id].spritesheetID);
if (!texture || texture->isInvalid)
continue;
vec2 uvMin = frame.crop / vec2(texture->size);
vec2 uvMax = (frame.crop + frame.size) / vec2(texture->size);
f32 vertices[] = UV_VERTICES(uvMin, uvMax);
mat4 model = quad_model_get(frame.size, frame.position, frame.pivot, frame.rotation, PERCENT_TO_UNIT(frame.scale));
mat4 layerTransform = transform * (rootModel * model);
vec4 tint = frame.tintRGBA;
tint.a *= U8_TO_FLOAT(self->settings->previewOverlayTransparency);
canvas_texture_draw(&self->canvas, shaderTexture, texture->id, layerTransform, vertices, tint, frame.offsetRGB);
}
} }
};
auto onionskins_draw = [&]()
{
if (!self->settings->onionskinIsEnabled) return;
onionskin_draw(self->settings->onionskinBeforeCount, -1, self->settings->onionskinBeforeColorOffset);
onionskin_draw(self->settings->onionskinAfterCount, 1, self->settings->onionskinAfterColorOffset);
};
if (self->settings->onionskinDrawOrder == ONIONSKIN_BELOW) onionskins_draw();
base_draw(self->time);
if (self->settings->onionskinDrawOrder == ONIONSKIN_ABOVE) onionskins_draw();
};
animation_draw(self->reference->animationID);
animation_draw(self->animationOverlayID);
canvas_unbind(); canvas_unbind();
} }

View File

@@ -7,6 +7,7 @@ enum RenderType
RENDER_PNG, RENDER_PNG,
RENDER_GIF, RENDER_GIF,
RENDER_WEBM, RENDER_WEBM,
RENDER_MP4,
RENDER_COUNT RENDER_COUNT
}; };
@@ -15,11 +16,13 @@ const inline std::string RENDER_TYPE_STRINGS[] =
"PNG Images", "PNG Images",
"GIF image", "GIF image",
"WebM video", "WebM video",
"MP4 video",
}; };
const inline std::string RENDER_EXTENSIONS[RENDER_COUNT] = const inline std::string RENDER_EXTENSIONS[RENDER_COUNT] =
{ {
".png", ".png",
".gif", ".gif",
".webm" ".webm",
".mp4",
}; };

View File

@@ -1,20 +1,8 @@
#include "resources.h" #include "resources.h"
void resources_texture_init(Resources* self, const std::string& path, s32 id)
{
Texture texture;
if (map_find(self->textures, id))
texture_free(&self->textures[id]);
texture_from_path_init(&texture, path);
self->textures[id] = texture;
}
void resources_init(Resources* self) 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++) for (s32 i = 0; i < SHADER_COUNT; i++)
shader_init(&self->shaders[i], SHADER_DATA[i].vertex, SHADER_DATA[i].fragment); shader_init(&self->shaders[i], SHADER_DATA[i].vertex, SHADER_DATA[i].fragment);
@@ -22,18 +10,8 @@ void resources_init(Resources* self)
void resources_free(Resources* self) void resources_free(Resources* self)
{ {
resources_textures_free(self);
for (auto& shader : self->shaders) for (auto& shader : self->shaders)
shader_free(&shader); shader_free(&shader);
texture_free(&self->atlas); texture_free(&self->atlas);
} }
void resources_textures_free(Resources* self)
{
for (auto& [id, texture] : self->textures)
texture_free(&self->textures[id]);
log_info(RESOURCES_TEXTURES_FREE_INFO);
}

View File

@@ -10,10 +10,7 @@ struct Resources
{ {
GLuint shaders[SHADER_COUNT]; GLuint shaders[SHADER_COUNT];
Texture atlas; Texture atlas;
std::map<s32, Texture> textures;
}; };
void resources_init(Resources* self); void resources_init(Resources* self);
void resources_texture_init(Resources* self, const std::string& path, s32 id);
void resources_free(Resources* self); void resources_free(Resources* self);
void resources_textures_free(Resources* self);

View File

@@ -6,77 +6,92 @@ static void _settings_setting_load(Settings* self, const std::string& line)
{ {
const auto& entry = SETTINGS_ENTRIES[i]; const auto& entry = SETTINGS_ENTRIES[i];
const std::string& key = entry.key; const std::string& key = entry.key;
void* target = (u8*)self + entry.offset; void* target = (u8*)self + entry.offset;
auto match_key = [&](const std::string& full) -> const char* auto match_key = [&](const std::string& full) -> const char*
{ {
if (!line.starts_with(full)) if (!line.starts_with(full))
return nullptr; return nullptr;
size_t p = full.size(); size_t p = full.size();
while (p < line.size() && std::isspace((u8)line[p])) ++p; while (p < line.size() && std::isspace((u8)line[p])) ++p;
if (p < line.size() && line[p] == '=') return line.c_str() + p + 1; if (p < line.size() && line[p] == '=')
return line.c_str() + p + 1;
return nullptr; return nullptr;
}; };
auto* value = match_key(key); const char* value = nullptr;
if (value)
{
switch (entry.type) switch (entry.type)
{ {
case TYPE_INT: case TYPE_INT:
*(s32*)target = std::atoi(value); if ((value = match_key(key))) { *(s32*)target = std::atoi(value); return; }
return;
case TYPE_BOOL:
*(bool*)target = string_to_bool(value);
return;
case TYPE_FLOAT:
*(f32*)target = std::atof(value);
return;
case TYPE_STRING:
*(std::string*)target = value;
return;
default:
break; break;
} case TYPE_BOOL:
} if ((value = match_key(key))) { *(bool*)target = string_to_bool(value); return; }
break;
if (entry.type == TYPE_VEC2) case TYPE_FLOAT:
{ if ((value = match_key(key))) { *(f32*)target = std::atof(value); return; }
vec2* v = (vec2*)target; break;
if ((value = match_key(key + "X"))) { v->x = std::atof(value); return; } case TYPE_STRING:
if ((value = match_key(key + "Y"))) { v->y = std::atof(value); return; } if ((value = match_key(key))) { *(std::string*)target = value; return; }
} break;
else if (entry.type == TYPE_IVEC2) case TYPE_IVEC2:
{ {
ivec2* v = (ivec2*)target; ivec2* v = (ivec2*)target;
if ((value = match_key(key + "X"))) { v->x = std::atoi(value); return; } if ((value = match_key(key + "X"))) { v->x = std::atoi(value); return; }
if ((value = match_key(key + "Y"))) { v->y = std::atoi(value); return; } if ((value = match_key(key + "Y"))) { v->y = std::atoi(value); return; }
break;
} }
else if (entry.type == TYPE_VEC3) case TYPE_IVEC2_WH:
{
ivec2* v = (ivec2*)target;
if ((value = match_key(key + "W"))) { v->x = std::atoi(value); return; }
if ((value = match_key(key + "H"))) { v->y = std::atoi(value); return; }
break;
};
case TYPE_VEC2:
{
vec2* v = (vec2*)target;
if ((value = match_key(key + "X"))) { v->x = std::atof(value); return; }
if ((value = match_key(key + "Y"))) { v->y = std::atof(value); return; }
break;
}
case TYPE_VEC2_WH:
{
vec2* v = (vec2*)target;
if ((value = match_key(key + "W"))) { v->x = std::atof(value); return; }
if ((value = match_key(key + "H"))) { v->y = std::atof(value); return; }
break;
};
case TYPE_VEC3:
{ {
vec3* v = (vec3*)target; vec3* v = (vec3*)target;
if ((value = match_key(key + "R"))) { v->x = std::atof(value); return; } if ((value = match_key(key + "R"))) { v->x = std::atof(value); return; }
if ((value = match_key(key + "G"))) { v->y = std::atof(value); return; } if ((value = match_key(key + "G"))) { v->y = std::atof(value); return; }
if ((value = match_key(key + "B"))) { v->z = std::atof(value); return; } if ((value = match_key(key + "B"))) { v->z = std::atof(value); return; }
break;
} }
else if (entry.type == TYPE_VEC4) case TYPE_VEC4:
{ {
vec4* v = (vec4*)target; vec4* v = (vec4*)target;
if ((value = match_key(key + "R"))) { v->x = std::atof(value); return; } if ((value = match_key(key + "R"))) { v->x = std::atof(value); return; }
if ((value = match_key(key + "G"))) { v->y = std::atof(value); return; } if ((value = match_key(key + "G"))) { v->y = std::atof(value); return; }
if ((value = match_key(key + "B"))) { v->z = std::atof(value); return; } if ((value = match_key(key + "B"))) { v->z = std::atof(value); return; }
if ((value = match_key(key + "A"))) { v->w = std::atof(value); return; } if ((value = match_key(key + "A"))) { v->w = std::atof(value); return; }
break;
}
default:
break;
} }
} }
log_warning(std::format(SETTINGS_VALUE_INIT_WARNING, line));
} }
std::string settings_path_get(void) std::string settings_path_get(void)
{ {
char* path = SDL_GetPrefPath("", SETTINGS_FOLDER); std::string filePath = preferences_path_get() + SETTINGS_PATH;
std::string filePath = std::string(path) + SETTINGS_PATH;
SDL_free(path);
return filePath; return filePath;
} }
@@ -113,6 +128,13 @@ static void _settings_setting_write(Settings* self, std::ostream& out, SettingsE
out << entry.key << "Y=" << data->y << "\n"; out << entry.key << "Y=" << data->y << "\n";
break; break;
} }
case TYPE_IVEC2_WH:
{
ivec2* data = (ivec2*)(selfPointer + entry.offset);
out << entry.key << "W=" << data->x << "\n";
out << entry.key << "H=" << data->y << "\n";
break;
}
case TYPE_VEC2: case TYPE_VEC2:
{ {
vec2* data = (vec2*)(selfPointer + entry.offset); vec2* data = (vec2*)(selfPointer + entry.offset);
@@ -120,6 +142,13 @@ static void _settings_setting_write(Settings* self, std::ostream& out, SettingsE
out << entry.key << "Y=" << std::format(SETTINGS_FLOAT_FORMAT, data->y) << "\n"; out << entry.key << "Y=" << std::format(SETTINGS_FLOAT_FORMAT, data->y) << "\n";
break; break;
} }
case TYPE_VEC2_WH:
{
vec2* data = (vec2*)(selfPointer + entry.offset);
out << entry.key << "W=" << std::format(SETTINGS_FLOAT_FORMAT, data->x) << "\n";
out << entry.key << "H=" << std::format(SETTINGS_FLOAT_FORMAT, data->y) << "\n";
break;
}
case TYPE_VEC3: case TYPE_VEC3:
{ {
vec3* data = (vec3*)(selfPointer + entry.offset); vec3* data = (vec3*)(selfPointer + entry.offset);
@@ -216,47 +245,32 @@ void settings_init(Settings* self)
{ {
const std::string path = settings_path_get(); const std::string path = settings_path_get();
std::ifstream file(path, std::ios::binary); std::ifstream file(path, std::ios::binary);
std::istream* in = nullptr;
std::istringstream defaultSettings;
if (file) if (file)
{
log_info(std::format(SETTINGS_INIT_INFO, path)); log_info(std::format(SETTINGS_INIT_INFO, path));
in = &file;
}
else else
{ {
log_error(std::format(SETTINGS_INIT_ERROR, path)); log_warning(std::format(SETTINGS_INIT_WARNING, path));
log_info(SETTINGS_DEFAULT_INFO); settings_save(self);
defaultSettings.str(SETTINGS_DEFAULT); std::ofstream out(path, std::ios::binary | std::ios::app);
in = &defaultSettings; out << SETTINGS_IMGUI_DEFAULT;
out.flush();
out.close();
file.open(path, std::ios::binary);
} }
std::string line; std::string line;
bool inSettingsSection = false; bool inSettingsSection = false;
while (std::getline(*in, line)) while (std::getline(file, line))
{ {
if (line == SETTINGS_SECTION) if (line == SETTINGS_SECTION)
{ {
inSettingsSection = true; inSettingsSection = true;
continue; continue;
} }
if (line.empty()) continue;
if (line == SETTINGS_SECTION_IMGUI) break; if (line == SETTINGS_SECTION_IMGUI) break;
if (inSettingsSection) _settings_setting_load(self, line); if (inSettingsSection) _settings_setting_load(self, line);
} }
// Save default settings
if (!file)
{
std::ofstream out(path, std::ios::binary | std::ios::trunc);
if (out)
{
out << SETTINGS_DEFAULT;
out.flush();
log_info(std::format(SETTINGS_SAVE_INFO, path));
}
else
log_error(std::format(SETTINGS_DEFAULT_ERROR, path));
}
} }

View File

@@ -4,17 +4,15 @@
#include "render.h" #include "render.h"
#include "tool.h" #include "tool.h"
#define SETTINGS_BUFFER 0xFFFF
#define SETTINGS_BUFFER_ITEM 0xFF
#define SETTINGS_SECTION "[Settings]" #define SETTINGS_SECTION "[Settings]"
#define SETTINGS_SECTION_IMGUI "# Dear ImGui" #define SETTINGS_SECTION_IMGUI "# Dear ImGui"
#define SETTINGS_INIT_ERROR "Failed to read settings file: {}" #define SETTINGS_INIT_WARNING "Unable to read settings file: {}; using default settings"
#define SETTINGS_DEFAULT_ERROR "Failed to write default settings file: {}" #define SETTINGS_INIT_ERROR "Unable to read settings file: {}"
#define SETTINGS_SAVE_ERROR "Failed to write settings file: {}" #define SETTINGS_SAVE_ERROR "Failed to write settings file: {}"
#define SETTINGS_SAVE_FINALIZE_ERROR "Failed to write settings file: {} ({})" #define SETTINGS_SAVE_FINALIZE_ERROR "Failed to write settings file: {} ({})"
#define SETTINGS_VALUE_INIT_WARNING "Unknown setting: {}"
#define SETTINGS_FLOAT_FORMAT "{:.3f}" #define SETTINGS_FLOAT_FORMAT "{:.3f}"
#define SETTINGS_INIT_INFO "Initialized settings from: {}" #define SETTINGS_INIT_INFO "Initialized settings from: {}"
#define SETTINGS_DEFAULT_INFO "Using default settings"
#define SETTINGS_DIRECTORY_ERROR "Failed to create settings directory: {} ({})" #define SETTINGS_DIRECTORY_ERROR "Failed to create settings directory: {} ({})"
#define SETTINGS_SAVE_INFO "Saved settings to: {}" #define SETTINGS_SAVE_INFO "Saved settings to: {}"
@@ -22,6 +20,147 @@
#define SETTINGS_PATH "settings.ini" #define SETTINGS_PATH "settings.ini"
#define SETTINGS_TEMPORARY_EXTENSION ".tmp" #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, {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") \
X(hotkeyOnionskin, HOTKEY_ONIONSKIN, TYPE_STRING, "O") \
X(hotkeyNew, HOTKEY_NEW, TYPE_STRING, "Ctrl+N") \
X(hotkeyOpen, HOTKEY_OPEN, TYPE_STRING, "Ctrl+O") \
X(hotkeySave, HOTKEY_SAVE, TYPE_STRING, "Ctrl+S") \
X(hotkeySaveAs, HOTKEY_SAVE_AS, TYPE_STRING, "Ctrl+Shift+S") \
X(hotkeyExit, HOTKEY_EXIT, TYPE_STRING, "Alt+F4") \
X(hotkeyPan, HOTKEY_PAN, TYPE_STRING, "P") \
X(hotkeyMove, HOTKEY_MOVE, TYPE_STRING, "V") \
X(hotkeyRotate, HOTKEY_ROTATE, TYPE_STRING, "R") \
X(hotkeyScale, HOTKEY_SCALE, TYPE_STRING, "S") \
X(hotkeyCrop, HOTKEY_CROP, TYPE_STRING, "C") \
X(hotkeyDraw, HOTKEY_DRAW, TYPE_STRING, "B") \
X(hotkeyErase, HOTKEY_ERASE, TYPE_STRING, "E") \
X(hotkeyColorPicker, HOTKEY_COLOR_PICKER, TYPE_STRING, "I") \
X(hotkeyUndo, HOTKEY_UNDO, TYPE_STRING, "Ctrl+Z") \
X(hotkeyRedo, HOTKEY_REDO, TYPE_STRING, "Ctrl+Shift+Z") \
X(hotkeyCopy, HOTKEY_COPY, TYPE_STRING, "Ctrl+C") \
X(hotkeyCut, HOTKEY_CUT, TYPE_STRING, "Ctrl+X") \
X(hotkeyPaste, HOTKEY_PASTE, TYPE_STRING, "Ctrl+V") \
\
X(playbackIsLoop, PLAYBACK_IS_LOOP, TYPE_BOOL, true) \
X(playbackIsClampPlayhead,PLAYBACK_IS_CLAMP_PLAYHEAD, TYPE_BOOL, true) \
\
X(changeIsCrop, CHANGE_IS_CROP, TYPE_BOOL, false) \
X(changeIsSize, CHANGE_IS_SIZE, TYPE_BOOL, false) \
X(changeIsPosition, CHANGE_IS_POSITION, TYPE_BOOL, false) \
X(changeIsPivot, CHANGE_IS_PIVOT, TYPE_BOOL, false) \
X(changeIsScale, CHANGE_IS_SCALE, TYPE_BOOL, false) \
X(changeIsRotation, CHANGE_IS_ROTATION, TYPE_BOOL, false) \
X(changeIsDelay, CHANGE_IS_DELAY, TYPE_BOOL, false) \
X(changeIsTint, CHANGE_IS_TINT, TYPE_BOOL, false) \
X(changeIsColorOffset, CHANGE_IS_COLOR_OFFSET, TYPE_BOOL, false) \
X(changeIsVisibleSet, CHANGE_IS_VISIBLE_SET, TYPE_BOOL, false) \
X(changeIsInterpolatedSet,CHANGE_IS_INTERPOLATED_SET, TYPE_BOOL, false) \
X(changeIsFromSelectedFrame,CHANGE_IS_FROM_SELECTED_FRAME,TYPE_BOOL, false) \
X(changeCrop, CHANGE_CROP, TYPE_VEC2, {}) \
X(changeSize, CHANGE_SIZE, TYPE_VEC2, {}) \
X(changePosition, CHANGE_POSITION, TYPE_VEC2, {}) \
X(changePivot, CHANGE_PIVOT, TYPE_VEC2, {}) \
X(changeScale, CHANGE_SCALE, TYPE_VEC2, {}) \
X(changeRotation, CHANGE_ROTATION, TYPE_FLOAT, 0.0f) \
X(changeDelay, CHANGE_DELAY, TYPE_INT, 0) \
X(changeTint, CHANGE_TINT, TYPE_VEC4, {}) \
X(changeColorOffset, CHANGE_COLOR_OFFSET, TYPE_VEC3, {}) \
X(changeIsVisible, CHANGE_IS_VISIBLE, TYPE_BOOL, false) \
X(changeIsInterpolated, CHANGE_IS_INTERPOLATED, TYPE_BOOL, false) \
X(changeNumberFrames, CHANGE_NUMBER_FRAMES, TYPE_INT, 1) \
\
X(scaleValue, SCALE_VALUE, TYPE_FLOAT, 1.0f) \
\
X(previewIsAxes, PREVIEW_IS_AXES, TYPE_BOOL, true) \
X(previewIsGrid, PREVIEW_IS_GRID, TYPE_BOOL, true) \
X(previewIsRootTransform, PREVIEW_IS_ROOT_TRANSFORM, TYPE_BOOL, true) \
X(previewIsTriggers, PREVIEW_IS_TRIGGERS, TYPE_BOOL, true) \
X(previewIsPivots, PREVIEW_IS_PIVOTS, TYPE_BOOL, false) \
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) \
X(previewZoom, PREVIEW_ZOOM, TYPE_FLOAT, 200.0f) \
X(previewPan, PREVIEW_PAN, TYPE_VEC2, {}) \
X(previewGridSize, PREVIEW_GRID_SIZE, TYPE_IVEC2, {32,32}) \
X(previewGridOffset, PREVIEW_GRID_OFFSET, TYPE_IVEC2, {}) \
X(previewGridColor, PREVIEW_GRID_COLOR, TYPE_VEC4, {1.0,1.0,1.0,0.125}) \
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, false) \
\
X(generateStartPosition, GENERATE_START_POSITION, TYPE_IVEC2, {}) \
X(generateSize, GENERATE_SIZE, TYPE_IVEC2, {64,64}) \
X(generatePivot, GENERATE_PIVOT, TYPE_IVEC2, {32,32}) \
X(generateRows, GENERATE_ROWS, TYPE_INT, 4) \
X(generateColumns, GENERATE_COLUMNS, TYPE_INT, 4) \
X(generateCount, GENERATE_COUNT, TYPE_INT, 16) \
X(generateDelay, GENERATE_DELAY, TYPE_INT, 1) \
\
X(editorIsGrid, EDITOR_IS_GRID, TYPE_BOOL, true) \
X(editorIsGridSnap, EDITOR_IS_GRID_SNAP, TYPE_BOOL, true) \
X(editorIsBorder, EDITOR_IS_BORDER, TYPE_BOOL, true) \
X(editorZoom, EDITOR_ZOOM, TYPE_FLOAT, 200.0f) \
X(editorPan, EDITOR_PAN, TYPE_VEC2, {0.0,0.0}) \
X(editorGridSize, EDITOR_GRID_SIZE, TYPE_IVEC2, {32,32}) \
X(editorGridOffset, EDITOR_GRID_OFFSET, TYPE_IVEC2, {32,32}) \
X(editorGridColor, EDITOR_GRID_COLOR, TYPE_VEC4, {1.0,1.0,1.0,0.125}) \
X(editorBackgroundColor, EDITOR_BACKGROUND_COLOR, TYPE_VEC4, {0.113,0.184,0.286,1.0}) \
\
X(mergeType, MERGE_TYPE, TYPE_INT, ANM2_MERGE_APPEND_FRAMES) \
X(mergeIsDeleteAnimationsAfter,MERGE_IS_DELETE_ANIMATIONS_AFTER,TYPE_BOOL, false) \
\
X(bakeInterval, BAKE_INTERVAL, TYPE_INT, 1) \
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, 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) \
\
X(tool, TOOL, TYPE_INT, TOOL_PAN) \
X(toolColor, TOOL_COLOR, TYPE_VEC4, {1.0,1.0,1.0,1.0}) \
\
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, SETTINGS_FFMPEG_PATH_VALUE_DEFAULT)
#define X(name, symbol, type, ...) \
const inline DATATYPE_TO_CTYPE(type) SETTINGS_##symbol##_DEFAULT = __VA_ARGS__;
SETTINGS_LIST
#undef X
struct Settings
{
#define X(name, symbol, type, ...) \
DATATYPE_TO_CTYPE(type) name = SETTINGS_##symbol##_DEFAULT;
SETTINGS_LIST
#undef X
};
struct SettingsEntry struct SettingsEntry
{ {
std::string key; std::string key;
@@ -29,270 +168,94 @@ struct SettingsEntry
s32 offset; s32 offset;
}; };
struct Settings const inline SettingsEntry SETTINGS_ENTRIES[] =
{ {
ivec2 windowSize = {1080, 720}; #define X(name, symbol, type, ...) \
bool playbackIsLoop = true; { #name, type, offsetof(Settings, name) },
bool playbackIsClampPlayhead = true; SETTINGS_LIST
bool changeIsCrop = false; #undef X
bool changeIsSize = false;
bool changeIsPosition = false;
bool changeIsPivot = false;
bool changeIsScale = false;
bool changeIsRotation = false;
bool changeIsDelay = false;
bool changeIsTint = false;
bool changeIsColorOffset = false;
bool changeIsVisibleSet = false;
bool changeIsInterpolatedSet = false;
bool changeIsFromSelectedFrame = false;
vec2 changeCrop{};
vec2 changeSize{};
vec2 changePosition{};
vec2 changePivot{};
vec2 changeScale{};
f32 changeRotation{};
s32 changeDelay{};
vec4 changeTint{};
vec3 changeColorOffset{};
bool changeIsVisible{};
bool changeIsInterpolated{};
s32 changeNumberFrames = 1;
f32 scaleValue = 1.0f;
bool previewIsAxes = true;
bool previewIsGrid = true;
bool previewIsRootTransform = false;
bool previewIsTriggers = true;
bool previewIsPivots = false;
bool previewIsTargets = true;
bool previewIsBorder = false;
f32 previewOverlayTransparency = 255.0f;
f32 previewZoom = 200.0;
vec2 previewPan = {0.0, 0.0};
ivec2 previewGridSize = {32, 3};
ivec2 previewGridOffset{};
vec4 previewGridColor = {1.0, 1.0, 1.0, 0.125};
vec4 previewAxesColor = {1.0, 1.0, 1.0, 0.125};
vec4 previewBackgroundColor = {0.113, 0.184, 0.286, 1.0};
ivec2 generateStartPosition = {0, 0};
ivec2 generateSize = {64, 64};
ivec2 generatePivot = {32, 32};
s32 generateRows = 4;
s32 generateColumns = 4;
s32 generateCount = 16;
s32 generateDelay = 1;
bool editorIsGrid = true;
bool editorIsGridSnap = true;
bool editorIsBorder = true;
f32 editorZoom = 200.0;
vec2 editorPan = {0.0, 0.0};
ivec2 editorGridSize = {32, 32};
ivec2 editorGridOffset = {32, 32};
vec4 editorGridColor = {1.0, 1.0, 1.0, 0.125};
vec4 editorBackgroundColor = {0.113, 0.184, 0.286, 1.0};
s32 mergeType = ANM2_MERGE_APPEND_FRAMES;
bool mergeIsDeleteAnimationsAfter = false;
s32 bakeInterval = 1;
bool bakeIsRoundScale = true;
bool bakeIsRoundRotation = true;
s32 tool = TOOL_PAN;
vec4 toolColor = {1.0, 1.0, 1.0, 1.0};
s32 renderType = RENDER_PNG;
std::string renderPath = ".";
std::string renderFormat = "{}.png";
std::string ffmpegPath{};
}; };
const SettingsEntry SETTINGS_ENTRIES[] =
{
{"window", TYPE_IVEC2, offsetof(Settings, windowSize)},
{"playbackIsLoop", TYPE_BOOL, offsetof(Settings, playbackIsLoop)},
{"playbackIsClampPlayhead", TYPE_BOOL, offsetof(Settings, playbackIsClampPlayhead)},
{"changeIsCrop", TYPE_BOOL, offsetof(Settings, changeIsCrop)},
{"changeIsSize", TYPE_BOOL, offsetof(Settings, changeIsSize)},
{"changeIsPosition", TYPE_BOOL, offsetof(Settings, changeIsPosition)},
{"changeIsPivot", TYPE_BOOL, offsetof(Settings, changeIsPivot)},
{"changeIsScale", TYPE_BOOL, offsetof(Settings, changeIsScale)},
{"changeIsRotation", TYPE_BOOL, offsetof(Settings, changeIsRotation)},
{"changeIsDelay", TYPE_BOOL, offsetof(Settings, changeIsDelay)},
{"changeIsTint", TYPE_BOOL, offsetof(Settings, changeIsTint)},
{"changeIsColorOffset", TYPE_BOOL, offsetof(Settings, changeIsColorOffset)},
{"changeIsVisibleSet", TYPE_BOOL, offsetof(Settings, changeIsVisibleSet)},
{"changeIsInterpolatedSet", TYPE_BOOL, offsetof(Settings, changeIsInterpolatedSet)},
{"changeIsFromSelectedFrame", TYPE_BOOL, offsetof(Settings, changeIsFromSelectedFrame)},
{"changeCrop", TYPE_VEC2, offsetof(Settings, changeCrop)},
{"changeSize", TYPE_VEC2, offsetof(Settings, changeSize)},
{"changePosition", TYPE_VEC2, offsetof(Settings, changePosition)},
{"changePivot", TYPE_VEC2, offsetof(Settings, changePivot)},
{"changeScale", TYPE_VEC2, offsetof(Settings, changeScale)},
{"changeRotation", TYPE_FLOAT, offsetof(Settings, changeRotation)},
{"changeDelay", TYPE_INT, offsetof(Settings, changeDelay)},
{"changeTint", TYPE_VEC4, offsetof(Settings, changeTint)},
{"changeColorOffset", TYPE_VEC3, offsetof(Settings, changeColorOffset)},
{"changeIsVisible", TYPE_BOOL, offsetof(Settings, changeIsVisibleSet)},
{"changeIsInterpolated", TYPE_BOOL, offsetof(Settings, changeIsInterpolatedSet)},
{"changeNumberFrames", TYPE_INT, offsetof(Settings, changeNumberFrames)},
{"scaleValue", TYPE_FLOAT, offsetof(Settings, scaleValue)},
{"previewIsAxes", TYPE_BOOL, offsetof(Settings, previewIsAxes)},
{"previewIsGrid", TYPE_BOOL, offsetof(Settings, previewIsGrid)},
{"previewIsRootTransform", TYPE_BOOL, offsetof(Settings, previewIsRootTransform)},
{"previewIsTriggers", TYPE_BOOL, offsetof(Settings, previewIsTriggers)},
{"previewIsPivots", TYPE_BOOL, offsetof(Settings, previewIsPivots)},
{"previewIsTargets", TYPE_BOOL, offsetof(Settings, previewIsTargets)},
{"previewIsBorder", TYPE_BOOL, offsetof(Settings, previewIsBorder)},
{"previewOverlayTransparency", TYPE_FLOAT, offsetof(Settings, previewOverlayTransparency)},
{"previewZoom", TYPE_FLOAT, offsetof(Settings, previewZoom)},
{"previewPan", TYPE_VEC2, offsetof(Settings, previewPan)},
{"previewGridSize", TYPE_IVEC2, offsetof(Settings, previewGridSize)},
{"previewGridOffset", TYPE_IVEC2, offsetof(Settings, previewGridOffset)},
{"previewGridColor", TYPE_VEC4, offsetof(Settings, previewGridColor)},
{"previewAxesColor", TYPE_VEC4, offsetof(Settings, previewAxesColor)},
{"previewBackgroundColor", TYPE_VEC4, offsetof(Settings, previewBackgroundColor)},
{"generateStartPosition", TYPE_IVEC2, offsetof(Settings, generateStartPosition)},
{"generateSize", TYPE_IVEC2, offsetof(Settings, generateSize)},
{"generatePivot", TYPE_IVEC2, offsetof(Settings, generatePivot)},
{"generateRows", TYPE_INT, offsetof(Settings, generateRows)},
{"generateColumns", TYPE_INT, offsetof(Settings, generateColumns)},
{"generateCount", TYPE_INT, offsetof(Settings, generateCount)},
{"generateDelay", TYPE_INT, offsetof(Settings, generateDelay)},
{"editorIsGrid", TYPE_BOOL, offsetof(Settings, editorIsGrid)},
{"editorIsGridSnap", TYPE_BOOL, offsetof(Settings, editorIsGridSnap)},
{"editorIsBorder", TYPE_BOOL, offsetof(Settings, editorIsBorder)},
{"editorZoom", TYPE_FLOAT, offsetof(Settings, editorZoom)},
{"editorPan", TYPE_VEC2, offsetof(Settings, editorPan)},
{"editorGridSize", TYPE_IVEC2, offsetof(Settings, editorGridSize)},
{"editorGridOffset", TYPE_IVEC2, offsetof(Settings, editorGridOffset)},
{"editorGridColor", TYPE_VEC4, offsetof(Settings, editorGridColor)},
{"editorBackgroundColor", TYPE_VEC4, offsetof(Settings, editorBackgroundColor)},
{"mergeType", TYPE_INT, offsetof(Settings, mergeType)},
{"mergeIsDeleteAnimationsAfter", TYPE_BOOL, offsetof(Settings, mergeIsDeleteAnimationsAfter)},
{"bakeInterval", TYPE_INT, offsetof(Settings, bakeInterval)},
{"bakeRoundScale", TYPE_BOOL, offsetof(Settings, bakeIsRoundScale)},
{"bakeRoundRotation", TYPE_BOOL, offsetof(Settings, bakeIsRoundRotation)},
{"tool", TYPE_INT, offsetof(Settings, tool)},
{"toolColor", TYPE_VEC4, offsetof(Settings, toolColor)},
{"renderType", TYPE_INT, offsetof(Settings, renderType)},
{"renderPath", TYPE_STRING, offsetof(Settings, renderPath)},
{"renderFormat", TYPE_STRING, offsetof(Settings, renderFormat)},
{"ffmpegPath", TYPE_STRING, offsetof(Settings, ffmpegPath)}
};
constexpr s32 SETTINGS_COUNT = (s32)std::size(SETTINGS_ENTRIES); constexpr s32 SETTINGS_COUNT = (s32)std::size(SETTINGS_ENTRIES);
const std::string SETTINGS_DEFAULT = R"( #define HOTKEY_LIST \
[Settings] X(NONE, "None") \
windowX=1920 X(CENTER_VIEW, "Center View") \
windowY=1080 X(FIT, "Fit") \
playbackIsLoop=true X(ZOOM_IN, "Zoom In") \
playbackIsClampPlayhead=false X(ZOOM_OUT, "Zoom Out") \
changeIsCrop=false X(PLAY_PAUSE, "Play/Pause") \
changeIsSize=false X(ONIONSKIN, "Onionskin") \
changeIsPosition=false X(NEW, "New") \
changeIsPivot=false X(OPEN, "Open") \
changeIsScale=false X(SAVE, "Save") \
changeIsRotation=false X(SAVE_AS, "Save As") \
changeIsDelay=false X(EXIT, "Exit") \
changeIsTint=false X(PAN, "Pan") \
changeIsColorOffset=false X(MOVE, "Move") \
changeIsVisibleSet=false X(ROTATE, "Rotate") \
changeIsInterpolatedSet=false X(SCALE, "Scale") \
changeIsFromSelectedFrame=false X(CROP, "Crop") \
changeCropX=0.000 X(DRAW, "Draw") \
changeCropY=0.000 X(ERASE, "Erase") \
changeSizeX=0.000 X(COLOR_PICKER, "Color Picker") \
changeSizeY=0.000 X(UNDO, "Undo") \
changePositionX=0.000 X(REDO, "Redo") \
changePositionY=0.000 X(COPY, "Copy") \
changePivotX=0.000 X(CUT, "Cut") \
changePivotY=0.000 X(PASTE, "Paste") \
changeScaleX=0.000
changeScaleY=0.000
changeRotation=0.000
changeDelay=1
changeTintR=0.000
changeTintG=0.000
changeTintB=0.000
changeTintA=0.000
changeColorOffsetR=0.000
changeColorOffsetG=0.000
changeColorOffsetB=0.000
changeIsVisible=false
changeIsInterpolated=false
changeNumberFrames=1
scaleValue=1.000
previewIsAxes=true
previewIsGrid=false
previewIsRootTransform=true
previewIsTriggers=false
previewIsPivots=false
previewIsTargets=true
previewIsBorder=false
previewOverlayTransparency=255.000
previewZoom=400.000
previewPanX=0.000
previewPanY=0.000
previewGridSizeX=32
previewGridSizeY=32
previewGridOffsetX=0
previewGridOffsetY=0
previewGridColorR=1.000
previewGridColorG=1.000
previewGridColorB=1.000
previewGridColorA=0.125
previewAxesColorR=1.000
previewAxesColorG=1.000
previewAxesColorB=1.000
previewAxesColorA=0.125
previewBackgroundColorR=0.114
previewBackgroundColorG=0.184
previewBackgroundColorB=0.286
previewBackgroundColorA=1.000
generateStartPositionX=0.000
generateStartPositionY=0.000
generateSizeX=0.000
generateSizeY=0.000
generatePivotX=0.000
generatePivotY=0.000
generateRows=4
generateColumns=4
generateCount=16
generateDelay=1
editorIsGrid=true
editorIsGridSnap=true
editorIsBorder=true
editorZoom=400.000
editorPanX=0.000
editorPanY=0.000
editorGridSizeX=32
editorGridSizeY=32
editorGridOffsetX=0
editorGridOffsetY=0
editorGridColorR=1.000
editorGridColorG=1.000
editorGridColorB=1.000
editorGridColorA=0.125
editorBackgroundColorR=0.113
editorBackgroundColorG=0.183
editorBackgroundColorB=0.286
editorBackgroundColorA=1.000
mergeType=1
mergeIsDeleteAnimationsAfter=false
bakeInterval=1
bakeRoundScale=true
bakeRoundRotation=true
tool=0
toolColorR=0.000
toolColorG=0.000
toolColorB=0.000
toolColorA=1.000
renderType=0
renderPath=.
renderFormat={}.png
ffmpegPath=/usr/bin/ffmpeg
typedef enum
{
#define X(name, str) HOTKEY_##name,
HOTKEY_LIST
#undef X
HOTKEY_COUNT
} HotkeyType;
const inline char* HOTKEY_STRINGS[] =
{
#define X(name, str) str,
HOTKEY_LIST
#undef X
};
using HotkeyMember = std::string Settings::*;
const inline HotkeyMember SETTINGS_HOTKEY_MEMBERS[HOTKEY_COUNT] =
{
nullptr,
&Settings::hotkeyCenterView,
&Settings::hotkeyFit,
&Settings::hotkeyZoomIn,
&Settings::hotkeyZoomOut,
&Settings::hotkeyPlayPause,
&Settings::hotkeyOnionskin,
&Settings::hotkeyNew,
&Settings::hotkeyOpen,
&Settings::hotkeySave,
&Settings::hotkeySaveAs,
&Settings::hotkeyExit,
&Settings::hotkeyPan,
&Settings::hotkeyMove,
&Settings::hotkeyRotate,
&Settings::hotkeyScale,
&Settings::hotkeyCrop,
&Settings::hotkeyDraw,
&Settings::hotkeyErase,
&Settings::hotkeyColorPicker,
&Settings::hotkeyUndo,
&Settings::hotkeyRedo,
&Settings::hotkeyCopy,
&Settings::hotkeyCut,
&Settings::hotkeyPaste
};
const std::string SETTINGS_IMGUI_DEFAULT = R"(
# Dear ImGui # Dear ImGui
[Window][## Window] [Window][## Window]
Pos=0,32 Pos=0,32
Size=1918,1032 Size=1600,868
Collapsed=0 Collapsed=0
[Window][Debug##Default] [Window][Debug##Default]
@@ -302,66 +265,86 @@ Collapsed=0
[Window][Tools] [Window][Tools]
Pos=8,40 Pos=8,40
Size=39,654 Size=38,516
Collapsed=0 Collapsed=0
DockId=0x0000000B,0 DockId=0x0000000B,0
[Window][Animations] [Window][Animations]
Pos=1452,388 Pos=1289,307
Size=458,306 Size=303,249
Collapsed=0 Collapsed=0
DockId=0x0000000A,0 DockId=0x0000000A,0
[Window][Events] [Window][Events]
Pos=1025,348 Pos=957,264
Size=425,346 Size=330,292
Collapsed=0 Collapsed=0
DockId=0x00000008,0 DockId=0x00000008,2
[Window][Spritesheets] [Window][Spritesheets]
Pos=1452,40 Pos=1289,40
Size=458,346 Size=303,265
Collapsed=0 Collapsed=0
DockId=0x00000009,0 DockId=0x00000009,0
[Window][Animation Preview] [Window][Animation Preview]
Pos=49,40 Pos=48,40
Size=974,654 Size=907,516
Collapsed=0 Collapsed=0
DockId=0x0000000C,0 DockId=0x0000000C,0
[Window][Spritesheet Editor] [Window][Spritesheet Editor]
Pos=49,40 Pos=48,40
Size=974,654 Size=907,516
Collapsed=0 Collapsed=0
DockId=0x0000000C,1 DockId=0x0000000C,1
[Window][Timeline] [Window][Timeline]
Pos=8,696 Pos=8,558
Size=1902,360 Size=1584,334
Collapsed=0 Collapsed=0
DockId=0x00000004,0 DockId=0x00000004,0
[Window][Frame Properties] [Window][Frame Properties]
Pos=1025,40 Pos=957,40
Size=425,306 Size=330,222
Collapsed=0 Collapsed=0
DockId=0x00000007,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] [Docking][Data]
DockSpace ID=0xFC02A410 Window=0x0E46F4F7 Pos=8,40 Size=1902,1016 Split=Y DockSpace ID=0xFC02A410 Window=0x0E46F4F7 Pos=8,40 Size=1584,852 Split=Y
DockNode ID=0x00000003 Parent=0xFC02A410 SizeRef=1902,654 Split=X DockNode ID=0x00000003 Parent=0xFC02A410 SizeRef=1902,680 Split=X
DockNode ID=0x00000001 Parent=0x00000003 SizeRef=1442,1016 Split=X Selected=0x024430EF DockNode ID=0x00000001 Parent=0x00000003 SizeRef=1017,1016 Split=X Selected=0x024430EF
DockNode ID=0x00000005 Parent=0x00000001 SizeRef=1015,654 Split=X Selected=0x024430EF DockNode ID=0x00000005 Parent=0x00000001 SizeRef=1264,654 Split=X Selected=0x024430EF
DockNode ID=0x0000000B Parent=0x00000005 SizeRef=39,654 Selected=0x18A5FDB9 DockNode ID=0x0000000B Parent=0x00000005 SizeRef=38,654 Selected=0x18A5FDB9
DockNode ID=0x0000000C Parent=0x00000005 SizeRef=974,654 CentralNode=1 Selected=0x024430EF DockNode ID=0x0000000C Parent=0x00000005 SizeRef=1224,654 CentralNode=1 Selected=0x024430EF
DockNode ID=0x00000006 Parent=0x00000001 SizeRef=425,654 Split=Y Selected=0x754E368F DockNode ID=0x00000006 Parent=0x00000001 SizeRef=330,654 Split=Y Selected=0x754E368F
DockNode ID=0x00000007 Parent=0x00000006 SizeRef=631,306 Selected=0x754E368F DockNode ID=0x00000007 Parent=0x00000006 SizeRef=631,293 Selected=0x754E368F
DockNode ID=0x00000008 Parent=0x00000006 SizeRef=631,346 Selected=0x8A65D963 DockNode ID=0x00000008 Parent=0x00000006 SizeRef=631,385 Selected=0xCD8384B1
DockNode ID=0x00000002 Parent=0x00000003 SizeRef=458,1016 Split=Y Selected=0x4EFD0020 DockNode ID=0x00000002 Parent=0x00000003 SizeRef=303,1016 Split=Y Selected=0x4EFD0020
DockNode ID=0x00000009 Parent=0x00000002 SizeRef=634,346 Selected=0x4EFD0020 DockNode ID=0x00000009 Parent=0x00000002 SizeRef=634,349 Selected=0x4EFD0020
DockNode ID=0x0000000A Parent=0x00000002 SizeRef=634,306 Selected=0xC1986EE2 DockNode ID=0x0000000A Parent=0x00000002 SizeRef=634,329 Selected=0xC1986EE2
DockNode ID=0x00000004 Parent=0xFC02A410 SizeRef=1902,360 Selected=0x4F89F0DC DockNode ID=0x00000004 Parent=0xFC02A410 SizeRef=1902,334 Selected=0x4F89F0DC
)"; )";
void settings_save(Settings* self); void settings_save(Settings* self);

View File

@@ -47,5 +47,7 @@ bool shader_init(GLuint* self, const std::string& vertex, const std::string& fra
void shader_free(GLuint* self) void shader_free(GLuint* self)
{ {
if (*self == GL_ID_NONE) return;
glDeleteProgram(*self); glDeleteProgram(*self);
} }

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "COMMON.h" #include "log.h"
#define SHADER_INFO_LOG_MAX 0xFF #define SHADER_INFO_LOG_MAX 0xFF
#define SHADER_INIT_ERROR "Failed to initialize shader {}:\n{}" #define SHADER_INIT_ERROR "Failed to initialize shader {}:\n{}"

View File

@@ -1,6 +1,6 @@
#include "snapshots.h" #include "snapshots.h"
static void _snapshot_stack_push(SnapshotStack* stack, const Snapshot* snapshot) static void _snapshot_stack_push(SnapshotStack* stack, Snapshot* snapshot)
{ {
if (stack->top >= SNAPSHOT_STACK_MAX) if (stack->top >= SNAPSHOT_STACK_MAX)
{ {
@@ -11,20 +11,29 @@ static void _snapshot_stack_push(SnapshotStack* stack, const Snapshot* snapshot)
stack->snapshots[stack->top++] = *snapshot; stack->snapshots[stack->top++] = *snapshot;
} }
static bool _snapshot_stack_pop(SnapshotStack* stack, Snapshot* snapshot) static Snapshot* _snapshot_stack_pop(SnapshotStack* stack)
{ {
if (stack->top == 0) return false; if (stack->top == 0) return nullptr;
return &stack->snapshots[--stack->top];
*snapshot = stack->snapshots[--stack->top];
return true;
} }
static void _snapshot_set(Snapshots* self, const Snapshot& snapshot) static void _snapshot_set(Snapshots* self, Snapshot* snapshot)
{ {
*self->anm2 = snapshot.anm2; if (!snapshot) return;
*self->reference = snapshot.reference;
self->preview->time = snapshot.time; *self->anm2 = snapshot->anm2;
self->action = snapshot.action; *self->reference = snapshot->reference;
self->preview->time = snapshot->time;
self->action = snapshot->action;
anm2_spritesheet_texture_pixels_upload(self->anm2);
}
Snapshot snapshot_get(Snapshots* self)
{
Snapshot snapshot = {*self->anm2, *self->reference, self->preview->time, self->action};
anm2_spritesheet_texture_pixels_download(&snapshot.anm2);
return snapshot;
} }
void snapshots_init(Snapshots* self, Anm2* anm2, Anm2Reference* reference, Preview* preview) void snapshots_init(Snapshots* self, Anm2* anm2, Anm2Reference* reference, Preview* preview)
@@ -41,18 +50,17 @@ void snapshots_reset(Snapshots* self)
self->action.clear(); self->action.clear();
} }
void snapshots_undo_push(Snapshots* self, const Snapshot* snapshot) void snapshots_undo_push(Snapshots* self, Snapshot* snapshot)
{ {
_snapshot_stack_push(&self->undoStack, snapshot);
self->redoStack.top = 0; self->redoStack.top = 0;
_snapshot_stack_push(&self->undoStack, snapshot);
} }
void snapshots_undo(Snapshots* self) void snapshots_undo(Snapshots* self)
{ {
Snapshot snapshot; if (Snapshot* snapshot = _snapshot_stack_pop(&self->undoStack))
if (_snapshot_stack_pop(&self->undoStack, &snapshot))
{ {
Snapshot current = {*self->anm2, *self->reference, self->preview->time, self->action}; Snapshot current = snapshot_get(self);
_snapshot_stack_push(&self->redoStack, &current); _snapshot_stack_push(&self->redoStack, &current);
_snapshot_set(self, snapshot); _snapshot_set(self, snapshot);
} }
@@ -60,10 +68,9 @@ void snapshots_undo(Snapshots* self)
void snapshots_redo(Snapshots* self) void snapshots_redo(Snapshots* self)
{ {
Snapshot snapshot; if (Snapshot* snapshot = _snapshot_stack_pop(&self->redoStack))
if (_snapshot_stack_pop(&self->redoStack, &snapshot))
{ {
Snapshot current = {*self->anm2, *self->reference, self->preview->time, self->action}; Snapshot current = snapshot_get(self);
_snapshot_stack_push(&self->undoStack, &current); _snapshot_stack_push(&self->undoStack, &current);
_snapshot_set(self, snapshot); _snapshot_set(self, snapshot);
} }

View File

@@ -4,7 +4,7 @@
#include "preview.h" #include "preview.h"
#include "texture.h" #include "texture.h"
#define SNAPSHOT_STACK_MAX 1000 #define SNAPSHOT_STACK_MAX 100
#define SNAPSHOT_ACTION "Action" #define SNAPSHOT_ACTION "Action"
struct Snapshot struct Snapshot
@@ -33,8 +33,9 @@ struct Snapshots
SnapshotStack redoStack; SnapshotStack redoStack;
}; };
void snapshots_undo_push(Snapshots* self, const Snapshot* snapshot); void snapshots_undo_push(Snapshots* self, Snapshot* snapshot);
void snapshots_init(Snapshots* self, Anm2* anm2, Anm2Reference* reference, Preview* preview); void snapshots_init(Snapshots* self, Anm2* anm2, Anm2Reference* reference, Preview* preview);
void snapshots_undo(Snapshots* self); void snapshots_undo(Snapshots* self);
void snapshots_redo(Snapshots* self); void snapshots_redo(Snapshots* self);
void snapshots_reset(Snapshots* self); void snapshots_reset(Snapshots* self);
Snapshot snapshot_get(Snapshots* self);

View File

@@ -22,20 +22,16 @@ static void _draw(State* self)
SDL_GL_SwapWindow(self->window); SDL_GL_SwapWindow(self->window);
} }
void init(State* self) bool sdl_init(State* self, bool isTestMode = false)
{ {
log_info(STATE_INIT_INFO);
settings_init(&self->settings);
if (!SDL_Init(SDL_INIT_VIDEO)) if (!SDL_Init(SDL_INIT_VIDEO))
{ {
log_error(std::format(STATE_SDL_INIT_ERROR, SDL_GetError())); log_error(std::format(STATE_SDL_INIT_ERROR, SDL_GetError()));
quit(self); quit(self);
return false;
} }
log_info(STATE_SDL_INIT_INFO); if (!isTestMode) log_info(STATE_SDL_INIT_INFO);
// Todo, when sdl3 mixer is released officially // Todo, when sdl3 mixer is released officially
/* /*
@@ -63,19 +59,39 @@ void init(State* self)
log_info(STATE_MIX_INIT_INFO); log_info(STATE_MIX_INIT_INFO);
*/ */
SDL_CreateWindowAndRenderer
if (isTestMode)
{
self->window = SDL_CreateWindow
( (
WINDOW_TITLE, WINDOW_TITLE,
self->settings.windowSize.x, WINDOW_TEST_MODE_SIZE.x, WINDOW_TEST_MODE_SIZE.y,
self->settings.windowSize.y, WINDOW_TEST_MODE_FLAGS
WINDOW_FLAGS,
&self->window,
&self->renderer
); );
}
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_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, STATE_GL_VERSION_MAJOR);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, STATE_GL_VERSION_MINOR);
self->glContext = SDL_GL_CreateContext(self->window); self->glContext = SDL_GL_CreateContext(self->window);
@@ -83,11 +99,19 @@ void init(State* self)
{ {
log_error(std::format(STATE_GL_CONTEXT_INIT_ERROR, SDL_GetError())); log_error(std::format(STATE_GL_CONTEXT_INIT_ERROR, SDL_GetError()));
quit(self); quit(self);
return false;
} }
glewInit(); if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
{
log_error(std::format(STATE_GLAD_INIT_ERROR));
quit(self);
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);
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@@ -96,13 +120,32 @@ void init(State* self)
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
glDisable(GL_LINE_SMOOTH); 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);
window_title_from_path_set(self->window, self->argument);
}
else
anm2_new(&self->anm2);
resources_init(&self->resources); resources_init(&self->resources);
dialog_init(&self->dialog, self->window); dialog_init(&self->dialog, self->window);
clipboard_init(&self->clipboard, &self->anm2); clipboard_init(&self->clipboard, &self->anm2);
snapshots_init(&self->snapshots, &self->anm2, &self->reference, &self->preview);
preview_init(&self->preview, &self->anm2, &self->reference, &self->resources, &self->settings); preview_init(&self->preview, &self->anm2, &self->reference, &self->resources, &self->settings);
generate_preview_init(&self->generatePreview, &self->anm2, &self->reference, &self->resources, &self->settings); generate_preview_init(&self->generatePreview, &self->anm2, &self->reference, &self->resources, &self->settings);
editor_init(&self->editor, &self->anm2, &self->reference, &self->resources, &self->settings); editor_init(&self->editor, &self->anm2, &self->reference, &self->resources, &self->settings);
snapshots_init(&self->snapshots, &self->anm2, &self->reference, &self->preview);
imgui_init imgui_init
( (
@@ -120,19 +163,12 @@ void init(State* self)
self->window, self->window,
&self->glContext &self->glContext
); );
if (!self->argument.empty())
{
anm2_deserialize(&self->anm2, &self->resources, self->argument);
window_title_from_path_set(self->window, self->argument);
}
else
anm2_new(&self->anm2);
} }
void loop(State* self) void loop(State* self)
{ {
self->tick = SDL_GetTicks(); self->tick = SDL_GetTicks();
self->update = self->tick;
while (self->tick > self->lastTick + TICK_DELAY) while (self->tick > self->lastTick + TICK_DELAY)
{ {
@@ -146,10 +182,28 @@ void loop(State* self)
self->lastTick = self->tick; self->lastTick = self->tick;
} }
if (self->settings.isVsync)
{
_update(self);
_draw(self);
}
else
{
while (self->update > self->lastUpdate + UPDATE_DELAY)
{
self->update = SDL_GetTicks();
if (self->update - self->lastUpdate < UPDATE_DELAY)
SDL_Delay(UPDATE_DELAY - (self->update - self->lastUpdate));
_update(self); _update(self);
_draw(self); _draw(self);
self->lastUpdate = self->update;
}
SDL_Delay(STATE_DELAY_MIN); SDL_Delay(STATE_DELAY_MIN);
}
} }
void quit(State* self) void quit(State* self)
@@ -170,4 +224,5 @@ void quit(State* self)
settings_save(&self->settings); settings_save(&self->settings);
log_info(STATE_QUIT_INFO); log_info(STATE_QUIT_INFO);
log_free();
} }

View File

@@ -2,13 +2,14 @@
#include "imgui.h" #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_ERROR "Failed to initialize SDL! {}"
#define STATE_SDL_INIT_INFO "Initialized SDL" #define STATE_SDL_INIT_INFO "Initialized SDL"
#define STATE_MIX_INIT_WARNING "Unable to initialize SDL_mixer! {}" #define STATE_MIX_INIT_WARNING "Unable to initialize SDL_mixer! {}"
#define STATE_MIX_AUDIO_DEVICE_INIT_WARNING "Unable to initialize audio device! {}" #define STATE_MIX_AUDIO_DEVICE_INIT_WARNING "Unable to initialize audio device! {}"
#define STATE_MIX_INIT_INFO "Initialized SDL_mixer" #define STATE_MIX_INIT_INFO "Initialized SDL_mixer"
#define STATE_GL_CONTEXT_INIT_ERROR "Failed to initialize OpenGL context! {}" #define STATE_GL_CONTEXT_INIT_ERROR "Failed to initialize OpenGL context! {}"
#define STATE_GLAD_INIT_ERROR "Failed to initialize GLAD!"
#define STATE_GL_CONTEXT_INIT_INFO "Initialized OpenGL context (OpenGL {})" #define STATE_GL_CONTEXT_INIT_INFO "Initialized OpenGL context (OpenGL {})"
#define STATE_QUIT_INFO "Exiting..." #define STATE_QUIT_INFO "Exiting..."
#define STATE_GL_LINE_WIDTH 2.0f #define STATE_GL_LINE_WIDTH 2.0f
@@ -23,10 +24,12 @@
#define STATE_MIX_DEVICE NULL #define STATE_MIX_DEVICE NULL
#define STATE_MIX_ALLOWED_CHANGES SDL_AUDIO_ALLOW_FORMAT_CHANGE #define STATE_MIX_ALLOWED_CHANGES SDL_AUDIO_ALLOW_FORMAT_CHANGE
#define STATE_GL_VERSION_MAJOR 3
#define STATE_GL_VERSION_MINOR 3
struct State struct State
{ {
SDL_Window* window; SDL_Window* window;
SDL_Renderer* renderer;
SDL_GLContext glContext; SDL_GLContext glContext;
Imgui imgui; Imgui imgui;
Dialog dialog; Dialog dialog;
@@ -43,9 +46,12 @@ struct State
std::string lastAction{}; std::string lastAction{};
u64 lastTick{}; u64 lastTick{};
u64 tick{}; u64 tick{};
u64 update{};
u64 lastUpdate{};
bool isRunning = true; bool isRunning = true;
}; };
bool sdl_init(State* self, bool isTestMode);
void init(State* state); void init(State* state);
void loop(State* state); void loop(State* state);
void quit(State* state); void quit(State* state);

View File

@@ -1,3 +1,10 @@
#if defined(__clang__) || defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#pragma GCC diagnostic ignored "-Wunused-function"
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#include "texture.h" #include "texture.h"
#define STBI_ONLY_PNG #define STBI_ONLY_PNG
@@ -10,7 +17,8 @@
static void _texture_gl_set(Texture* self, const u8* data) static void _texture_gl_set(Texture* self, const u8* data)
{ {
glGenTextures(1, &self->id); if (self->id == GL_ID_NONE) glGenTextures(1, &self->id);
glBindTexture(GL_TEXTURE_2D, 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); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self->size.x, self->size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
@@ -26,6 +34,7 @@ std::vector<u8> texture_download(const Texture* self)
glBindTexture(GL_TEXTURE_2D, self->id); glBindTexture(GL_TEXTURE_2D, self->id);
glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
return pixels; return pixels;
@@ -33,19 +42,15 @@ std::vector<u8> texture_download(const Texture* self)
bool texture_from_path_init(Texture* self, const std::string& path) bool texture_from_path_init(Texture* self, const std::string& path)
{ {
*self = Texture{}; u8* data = stbi_load(path.c_str(), &self->size.x, &self->size.y, nullptr, TEXTURE_CHANNELS);
u8* data = stbi_load(path.c_str(), &self->size.x, &self->size.y, &self->channels, 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) if (!data)
{ {
log_error(std::format(TEXTURE_INIT_ERROR, path)); log_error(std::format(TEXTURE_INIT_ERROR, path));
self->isInvalid = true;
return false; return false;
} }
}
self->isInvalid = false;
log_info(std::format(TEXTURE_INIT_INFO, path)); log_info(std::format(TEXTURE_INIT_INFO, path));
@@ -54,13 +59,12 @@ bool texture_from_path_init(Texture* self, const std::string& path)
return true; 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 = Texture{};
self->size = size; 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) if (!textureData)
{ {
@@ -73,11 +77,11 @@ bool texture_from_encoded_data_init(Texture* self, ivec2 size, s32 channels, con
return true; 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 = Texture{};
self->size = size; self->size = size;
self->channels = channels; self->isInvalid = false;
_texture_gl_set(self, data); _texture_gl_set(self, data);
@@ -87,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 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); bool isSuccess = stbi_write_png(path.c_str(), size.x, size.y, TEXTURE_CHANNELS, data, size.x * TEXTURE_CHANNELS);
if (!isSuccess) if (!isSuccess) log_error(std::format(TEXTURE_SAVE_ERROR, path));
{ else log_info(std::format(TEXTURE_SAVE_INFO, path));
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));
return isSuccess; return isSuccess;
} }
@@ -105,6 +104,8 @@ bool texture_from_gl_write(Texture* self, const std::string& path)
void texture_free(Texture* self) void texture_free(Texture* self)
{ {
if (self->isInvalid) return;
glDeleteTextures(1, &self->id); glDeleteTextures(1, &self->id);
*self = Texture{}; *self = Texture{};
} }
@@ -126,33 +127,3 @@ bool texture_pixel_set(Texture* self, ivec2 position, vec4 color)
return true; return true;
} }
Texture texture_copy(Texture* self)
{
Texture copy = *self;
_texture_gl_set(&copy, nullptr);
GLuint fboSource;
GLuint fboDestination;
glGenFramebuffers(1, &fboSource);
glGenFramebuffers(1, &fboDestination);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fboSource);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self->id, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboDestination);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, copy.id, 0);
glBlitFramebuffer
(
0, 0, self->size.x, self->size.y,
0, 0, self->size.x, self->size.y,
GL_COLOR_BUFFER_BIT, GL_NEAREST
);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glDeleteFramebuffers(1, &fboSource);
glDeleteFramebuffers(1, &fboDestination);
return copy;
}

View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "COMMON.h" #include "log.h"
#define TEXTURE_CHANNELS 4 #define TEXTURE_CHANNELS 4
#define TEXTURE_INIT_INFO "Initialized texture from file: {}" #define TEXTURE_INIT_INFO "Initialized texture from file: {}"
@@ -10,18 +10,18 @@
struct Texture struct Texture
{ {
GLuint id = 0; GLuint id = GL_ID_NONE;
ivec2 size = {0, 0}; ivec2 size{};
s32 channels = -1; bool isInvalid = true;
bool isInvalid = false;
auto operator<=>(const Texture&) const = default;
}; };
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_gl_write(Texture* self, const std::string& path);
bool texture_from_path_init(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_from_rgba_write(const std::string& path, const u8* data, ivec2 size);
bool texture_pixel_set(Texture* self, ivec2 position, vec4 color); bool texture_pixel_set(Texture* self, ivec2 position, vec4 color);
void texture_free(Texture* self); void texture_free(Texture* self);
std::vector<u8> texture_download(const Texture* self); std::vector<u8> texture_download(const Texture* self);
Texture texture_copy(Texture* self);

View File

@@ -7,3 +7,11 @@ void window_title_from_path_set(SDL_Window* self, const std::string& path)
else else
SDL_SetWindowTitle(self, WINDOW_TITLE); SDL_SetWindowTitle(self, WINDOW_TITLE);
} }
void window_vsync_set(bool isVsync)
{
if (isVsync)
SDL_GL_SetSwapInterval(1);
else
SDL_GL_SetSwapInterval(0);
}

View File

@@ -5,6 +5,10 @@
#define WINDOW_TITLE "Anm2Ed" #define WINDOW_TITLE "Anm2Ed"
#define WINDOW_TITLE_FORMAT "Anm2Ed ({})" #define WINDOW_TITLE_FORMAT "Anm2Ed ({})"
#define WINDOW_FLAGS SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL #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); 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); bool window_color_from_position_get(SDL_Window* self, vec2 position, vec4* color);
void window_vsync_set(bool isVsync);

View File

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

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