diff --git a/.gitmodules b/.gitmodules index b1f95ec..39508a8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/external/SDL_mixer b/external/SDL_mixer new file mode 160000 index 0000000..8c516fc --- /dev/null +++ b/external/SDL_mixer @@ -0,0 +1 @@ +Subproject commit 8c516fcd2eca48262188426fbe75365ba4326346 diff --git a/src/anm2.cpp b/src/anm2.cpp index 3ec5acf..f7e644b 100644 --- a/src/anm2.cpp +++ b/src/anm2.cpp @@ -1356,12 +1356,28 @@ namespace anm2ed::anm2 return unused; } + std::vector Anm2::event_names_get() + { + std::vector names{}; + for (auto& event : content.events | std::views::values) + names.push_back(event.name); + return names; + } + + std::vector Anm2::animation_names_get() + { + std::vector names{}; + for (auto& animation : animations.items) + names.push_back(animation.name); + return names; + } + std::vector Anm2::spritesheet_names_get() { - std::vector spritesheets{}; + std::vector 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) diff --git a/src/anm2.h b/src/anm2.h index 056cc84..afcdea9 100644 --- a/src/anm2.h +++ b/src/anm2.h @@ -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 events_unused(Reference = REFERENCE_DEFAULT); std::set layers_unused(Reference = REFERENCE_DEFAULT); std::set nulls_unused(Reference = REFERENCE_DEFAULT); + std::vector animation_names_get(); std::vector spritesheet_names_get(); + std::vector 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); }; diff --git a/src/canvas.cpp b/src/canvas.cpp index 2776404..6359da4 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -1,11 +1,15 @@ #include "canvas.h" -#include "math.h" +#include +#include #include #include #include #include +#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 Canvas::pixels_get() + { + auto count = size.x * size.y * texture::CHANNELS; + std::vector 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); diff --git a/src/canvas.h b/src/canvas.h index 925c62d..cb99da7 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -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 pixels_get(); }; } diff --git a/src/snapshots.cpp b/src/snapshots.cpp index a456be6..b473529 100644 --- a/src/snapshots.cpp +++ b/src/snapshots.cpp @@ -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)); 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; diff --git a/src/spritesheet_editor.cpp b/src/spritesheet_editor.cpp index ff10925..5861fbd 100644 --- a/src/spritesheet_editor.cpp +++ b/src/spritesheet_editor.cpp @@ -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; } diff --git a/src/texture.cpp b/src/texture.cpp index 306889a..f508c47 100644 --- a/src/texture.cpp +++ b/src/texture.cpp @@ -32,12 +32,20 @@ namespace anm2ed::texture return id != 0; } + std::vector 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.x) * static_cast(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(this)->download(); + } + void Texture::bind(GLuint unit) { glActiveTexture(GL_TEXTURE0 + unit); diff --git a/src/texture.h b/src/texture.h index a5e5b0e..419c64c 100644 --- a/src/texture.h +++ b/src/texture.h @@ -17,7 +17,8 @@ namespace anm2ed::texture glm::ivec2 size{}; GLint filter = GL_NEAREST; int channels{}; - std::vector pixels{}; + mutable std::vector 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 pixels_get(); void pixel_line(glm::ivec2, glm::ivec2, glm::vec4); void bind(GLuint = 0); void unbind(GLuint = 0);