FFmpeg and sound support; refactor, etc.
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -14,3 +14,6 @@
|
||||
[submodule "external/lunasvg"]
|
||||
path = external/lunasvg
|
||||
url = https://github.com/sammycage/lunasvg
|
||||
[submodule "external/SDL_mixer"]
|
||||
path = external/SDL_mixer
|
||||
url = https://github.com/libsdl-org/SDL_mixer
|
||||
|
||||
1
external/SDL_mixer
vendored
Submodule
1
external/SDL_mixer
vendored
Submodule
Submodule external/SDL_mixer added at 8c516fcd2e
22
src/anm2.cpp
22
src/anm2.cpp
@@ -1356,12 +1356,28 @@ namespace anm2ed::anm2
|
||||
return unused;
|
||||
}
|
||||
|
||||
std::vector<std::string> Anm2::event_names_get()
|
||||
{
|
||||
std::vector<std::string> names{};
|
||||
for (auto& event : content.events | std::views::values)
|
||||
names.push_back(event.name);
|
||||
return names;
|
||||
}
|
||||
|
||||
std::vector<std::string> Anm2::animation_names_get()
|
||||
{
|
||||
std::vector<std::string> names{};
|
||||
for (auto& animation : animations.items)
|
||||
names.push_back(animation.name);
|
||||
return names;
|
||||
}
|
||||
|
||||
std::vector<std::string> Anm2::spritesheet_names_get()
|
||||
{
|
||||
std::vector<std::string> spritesheets{};
|
||||
std::vector<std::string> names{};
|
||||
for (auto& [id, spritesheet] : content.spritesheets)
|
||||
spritesheets.push_back(std::format("#{} {}", id, spritesheet.path.c_str()));
|
||||
return spritesheets;
|
||||
names.push_back(std::format(SPRITESHEET_FORMAT, id, spritesheet.path.c_str()));
|
||||
return names;
|
||||
}
|
||||
|
||||
void Anm2::bake(Reference reference, int interval, bool isRoundScale, bool isRoundRotation)
|
||||
|
||||
@@ -24,7 +24,8 @@ namespace anm2ed::anm2
|
||||
constexpr auto NO_PATH = "(No Path)";
|
||||
constexpr auto LAYER_FORMAT = "#{} {} (Spritesheet: #{})";
|
||||
constexpr auto NULL_FORMAT = "#{} {}";
|
||||
constexpr auto SPRITESHEET_FORMAT = "#%d %s";
|
||||
constexpr auto SPRITESHEET_FORMAT_C = "#%d %s";
|
||||
constexpr auto SPRITESHEET_FORMAT = "#{} {}";
|
||||
|
||||
enum Type
|
||||
{
|
||||
@@ -254,7 +255,9 @@ namespace anm2ed::anm2
|
||||
std::set<int> events_unused(Reference = REFERENCE_DEFAULT);
|
||||
std::set<int> layers_unused(Reference = REFERENCE_DEFAULT);
|
||||
std::set<int> nulls_unused(Reference = REFERENCE_DEFAULT);
|
||||
std::vector<std::string> animation_names_get();
|
||||
std::vector<std::string> spritesheet_names_get();
|
||||
std::vector<std::string> event_names_get();
|
||||
void bake(Reference, int = 1, bool = true, bool = true);
|
||||
void generate_from_grid(Reference, glm::ivec2, glm::ivec2, glm::ivec2, int, int, int);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#include "canvas.h"
|
||||
|
||||
#include "math.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <glm/ext/matrix_clip_space.hpp>
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <glm/gtc/matrix_inverse.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "math.h"
|
||||
#include "texture.h"
|
||||
|
||||
using namespace glm;
|
||||
using namespace anm2ed::shader;
|
||||
|
||||
@@ -283,6 +287,21 @@ namespace anm2ed::canvas
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
std::vector<unsigned char> Canvas::pixels_get()
|
||||
{
|
||||
auto count = size.x * size.y * texture::CHANNELS;
|
||||
std::vector<unsigned char> pixels(count);
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
|
||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
glReadPixels(0, 0, size.x, size.y, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
|
||||
return pixels;
|
||||
}
|
||||
|
||||
void Canvas::zoom_set(float& zoom, vec2& pan, vec2 focus, float step)
|
||||
{
|
||||
auto zoomFactor = math::percent_to_unit(zoom);
|
||||
@@ -295,6 +314,19 @@ namespace anm2ed::canvas
|
||||
}
|
||||
}
|
||||
|
||||
vec4 Canvas::pixel_read(vec2 position, vec2 framebufferSize)
|
||||
{
|
||||
uint8_t rgba[4]{};
|
||||
|
||||
glBindTexture(GL_READ_FRAMEBUFFER, fbo);
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
||||
glReadPixels(position.x, framebufferSize.y - 1 - position.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
|
||||
return vec4(math::uint8_to_float(rgba[0]), math::uint8_to_float(rgba[1]), math::uint8_to_float(rgba[2]),
|
||||
math::uint8_to_float(rgba[3]));
|
||||
}
|
||||
|
||||
vec2 Canvas::position_translate(float& zoom, vec2& pan, vec2 position)
|
||||
{
|
||||
auto zoomFactor = math::percent_to_unit(zoom);
|
||||
|
||||
@@ -18,6 +18,9 @@ namespace anm2ed::canvas
|
||||
constexpr auto DASH_GAP = 1.0f;
|
||||
constexpr auto DASH_OFFSET = 1.0f;
|
||||
|
||||
constexpr auto STEP = 1.0f;
|
||||
constexpr auto STEP_FAST = 5.0f;
|
||||
|
||||
class Canvas
|
||||
{
|
||||
public:
|
||||
@@ -43,6 +46,7 @@ namespace anm2ed::canvas
|
||||
void framebuffer_set();
|
||||
void framebuffer_resize_check();
|
||||
void size_set(glm::vec2);
|
||||
glm::vec4 pixel_read(glm::vec2, glm::vec2);
|
||||
glm::mat4 transform_get(float = 100.0f, glm::vec2 = {});
|
||||
void axes_render(shader::Shader&, float, glm::vec2, glm::vec4 = glm::vec4(1.0f));
|
||||
void grid_render(shader::Shader&, float, glm::vec2, glm::ivec2 = glm::ivec2(32, 32), glm::ivec2 = {},
|
||||
@@ -58,5 +62,6 @@ namespace anm2ed::canvas
|
||||
void zoom_set(float&, glm::vec2&, glm::vec2, float);
|
||||
glm::vec2 position_translate(float&, glm::vec2&, glm::vec2);
|
||||
void set_to_rect(float& zoom, glm::vec2& pan, glm::vec4 rect);
|
||||
std::vector<unsigned char> pixels_get();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,15 @@
|
||||
|
||||
namespace anm2ed::snapshots
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void textures_ensure(anm2::Anm2& anm2)
|
||||
{
|
||||
for (auto& [id, spritesheet] : anm2.content.spritesheets)
|
||||
spritesheet.texture.ensure_pixels();
|
||||
}
|
||||
}
|
||||
|
||||
bool SnapshotStack::is_empty()
|
||||
{
|
||||
return top == 0;
|
||||
@@ -31,6 +40,7 @@ namespace anm2ed::snapshots
|
||||
|
||||
void Snapshots::push(const anm2::Anm2& anm2, anm2::Reference reference, const std::string& message)
|
||||
{
|
||||
textures_ensure(const_cast<anm2::Anm2&>(anm2));
|
||||
Snapshot snapshot = {anm2, reference, message};
|
||||
undoStack.push(snapshot);
|
||||
redoStack.clear();
|
||||
@@ -40,8 +50,10 @@ namespace anm2ed::snapshots
|
||||
{
|
||||
if (auto current = undoStack.pop())
|
||||
{
|
||||
textures_ensure(anm2);
|
||||
Snapshot snapshot = {anm2, reference, message};
|
||||
redoStack.push(snapshot);
|
||||
textures_ensure(current->anm2);
|
||||
anm2 = current->anm2;
|
||||
reference = current->reference;
|
||||
message = current->message;
|
||||
@@ -52,8 +64,10 @@ namespace anm2ed::snapshots
|
||||
{
|
||||
if (auto current = redoStack.pop())
|
||||
{
|
||||
textures_ensure(anm2);
|
||||
Snapshot snapshot = {anm2, reference, message};
|
||||
undoStack.push(snapshot);
|
||||
textures_ensure(current->anm2);
|
||||
anm2 = current->anm2;
|
||||
reference = current->reference;
|
||||
message = current->message;
|
||||
|
||||
@@ -14,6 +14,8 @@ using namespace glm;
|
||||
|
||||
namespace anm2ed::spritesheet_editor
|
||||
{
|
||||
constexpr auto PIVOT_COLOR = color::PINK;
|
||||
|
||||
SpritesheetEditor::SpritesheetEditor() : Canvas(vec2())
|
||||
{
|
||||
}
|
||||
@@ -115,7 +117,7 @@ namespace anm2ed::spritesheet_editor
|
||||
|
||||
auto pivotTransform =
|
||||
transform * math::quad_model_get(canvas::PIVOT_SIZE, frame->crop + frame->pivot, PIVOT_SIZE * 0.5f);
|
||||
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, color::RED);
|
||||
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, PIVOT_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +147,7 @@ namespace anm2ed::spritesheet_editor
|
||||
auto isUp = imgui::chord_repeating(ImGuiKey_UpArrow);
|
||||
auto isDown = imgui::chord_repeating(ImGuiKey_DownArrow);
|
||||
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
|
||||
auto step = isMod ? step::FAST : step::NORMAL;
|
||||
auto step = isMod ? canvas::STEP_FAST : canvas::STEP;
|
||||
auto useTool = tool;
|
||||
auto isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
|
||||
auto isMouseReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
|
||||
@@ -195,12 +197,26 @@ namespace anm2ed::spritesheet_editor
|
||||
case tool::ERASE:
|
||||
{
|
||||
if (!spritesheet) break;
|
||||
if (isMouseClicked) document.snapshot(tool == tool::DRAW ? "Draw" : "Erase");
|
||||
auto color = tool == tool::DRAW ? toolColor : vec4();
|
||||
if (isMouseClicked) document.snapshot(tool == tool::DRAW ? "Draw" : "Erase");
|
||||
if (isMouseDown) spritesheet->texture.pixel_line(ivec2(previousMousePos), ivec2(mousePos), color);
|
||||
if (isMouseReleased) document.change(change::FRAMES);
|
||||
break;
|
||||
}
|
||||
case tool::COLOR_PICKER:
|
||||
{
|
||||
if (isMouseDown)
|
||||
{
|
||||
auto position = to_vec2(ImGui::GetMousePos());
|
||||
toolColor = pixel_read(position, {settings.windowSize.x, settings.windowSize.y});
|
||||
if (ImGui::BeginTooltip())
|
||||
{
|
||||
ImGui::ColorButton("##Color Picker Button", to_imvec4(toolColor));
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -32,12 +32,20 @@ namespace anm2ed::texture
|
||||
return id != 0;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Texture::pixels_get()
|
||||
{
|
||||
ensure_pixels();
|
||||
return pixels;
|
||||
}
|
||||
|
||||
void Texture::download()
|
||||
{
|
||||
pixels.resize(size.x * size.y * CHANNELS);
|
||||
if (size.x <= 0 || size.y <= 0 || !is_valid()) return;
|
||||
pixels.resize(static_cast<size_t>(size.x) * static_cast<size_t>(size.y) * CHANNELS);
|
||||
glBindTexture(GL_TEXTURE_2D, id);
|
||||
glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
isPixelsDirty = false;
|
||||
}
|
||||
|
||||
void Texture::upload(const uint8_t* data)
|
||||
@@ -66,6 +74,7 @@ namespace anm2ed::texture
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
isPixelsDirty = false;
|
||||
}
|
||||
|
||||
Texture::Texture() = default;
|
||||
@@ -91,6 +100,7 @@ namespace anm2ed::texture
|
||||
{
|
||||
if (is_valid()) glDeleteTextures(1, &id);
|
||||
id = 0;
|
||||
other.ensure_pixels();
|
||||
size = other.size;
|
||||
filter = other.filter;
|
||||
channels = other.channels;
|
||||
@@ -110,6 +120,9 @@ namespace anm2ed::texture
|
||||
filter = other.filter;
|
||||
channels = other.channels;
|
||||
pixels = std::move(other.pixels);
|
||||
isPixelsDirty = other.isPixelsDirty;
|
||||
other.isPixelsDirty = true;
|
||||
if (!pixels.empty()) upload();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@@ -129,9 +142,15 @@ namespace anm2ed::texture
|
||||
upload(bitmap.data());
|
||||
}
|
||||
|
||||
Texture::Texture(const uint8_t* data, ivec2 size)
|
||||
{
|
||||
this->size = size;
|
||||
upload(data);
|
||||
}
|
||||
|
||||
Texture::Texture(const std::string& pngPath)
|
||||
{
|
||||
if (const uint8* data = stbi_load(pngPath.c_str(), &size.x, &size.y, nullptr, CHANNELS); data)
|
||||
if (const uint8_t* data = stbi_load(pngPath.c_str(), &size.x, &size.y, nullptr, CHANNELS); data)
|
||||
{
|
||||
upload(data);
|
||||
stbi_image_free((void*)data);
|
||||
@@ -140,6 +159,7 @@ namespace anm2ed::texture
|
||||
|
||||
bool Texture::write_png(const std::string& path)
|
||||
{
|
||||
ensure_pixels();
|
||||
return stbi_write_png(path.c_str(), size.x, size.y, CHANNELS, this->pixels.data(), size.x * CHANNELS);
|
||||
}
|
||||
|
||||
@@ -147,6 +167,7 @@ namespace anm2ed::texture
|
||||
{
|
||||
if (position.x < 0 || position.y < 0 || position.x >= size.x || position.y >= size.y) return;
|
||||
|
||||
ensure_pixels();
|
||||
uint8 rgba8[4] = {(uint8)float_to_uint8(color.r), (uint8)float_to_uint8(color.g), (uint8)float_to_uint8(color.b),
|
||||
(uint8)float_to_uint8(color.a)};
|
||||
|
||||
@@ -154,14 +175,23 @@ namespace anm2ed::texture
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexSubImage2D(GL_TEXTURE_2D, 0, position.x, position.y, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, rgba8);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
if (!pixels.empty())
|
||||
{
|
||||
auto index = (position.y * size.x + position.x) * CHANNELS;
|
||||
pixels[index + 0] = rgba8[0];
|
||||
pixels[index + 1] = rgba8[1];
|
||||
pixels[index + 2] = rgba8[2];
|
||||
pixels[index + 3] = rgba8[3];
|
||||
isPixelsDirty = false;
|
||||
}
|
||||
else
|
||||
isPixelsDirty = true;
|
||||
}
|
||||
|
||||
void Texture::pixel_line(ivec2 start, ivec2 end, vec4 color)
|
||||
{
|
||||
auto plot = [&](ivec2 pos)
|
||||
{
|
||||
pixel_set(pos, color);
|
||||
};
|
||||
ensure_pixels();
|
||||
auto plot = [&](ivec2 pos) { pixel_set(pos, color); };
|
||||
|
||||
int x0 = start.x;
|
||||
int y0 = start.y;
|
||||
@@ -192,6 +222,13 @@ namespace anm2ed::texture
|
||||
}
|
||||
}
|
||||
|
||||
void Texture::ensure_pixels() const
|
||||
{
|
||||
if (size.x <= 0 || size.y <= 0) return;
|
||||
if (!pixels.empty() && !isPixelsDirty) return;
|
||||
const_cast<Texture*>(this)->download();
|
||||
}
|
||||
|
||||
void Texture::bind(GLuint unit)
|
||||
{
|
||||
glActiveTexture(GL_TEXTURE0 + unit);
|
||||
|
||||
@@ -17,7 +17,8 @@ namespace anm2ed::texture
|
||||
glm::ivec2 size{};
|
||||
GLint filter = GL_NEAREST;
|
||||
int channels{};
|
||||
std::vector<uint8_t> pixels{};
|
||||
mutable std::vector<uint8_t> pixels{};
|
||||
mutable bool isPixelsDirty{true};
|
||||
|
||||
bool is_valid();
|
||||
void download();
|
||||
@@ -30,10 +31,13 @@ namespace anm2ed::texture
|
||||
Texture(Texture&&);
|
||||
Texture& operator=(const Texture&);
|
||||
Texture& operator=(Texture&&);
|
||||
Texture(const uint8_t*, glm::ivec2);
|
||||
Texture(const char*, size_t, glm::ivec2);
|
||||
Texture(const std::string&);
|
||||
bool write_png(const std::string&);
|
||||
void pixel_set(glm::ivec2, glm::vec4);
|
||||
void ensure_pixels() const;
|
||||
std::vector<uint8_t> pixels_get();
|
||||
void pixel_line(glm::ivec2, glm::ivec2, glm::vec4);
|
||||
void bind(GLuint = 0);
|
||||
void unbind(GLuint = 0);
|
||||
|
||||
Reference in New Issue
Block a user