diff --git a/src/canvas.cpp b/src/canvas.cpp index 7018b65..19560a0 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -104,7 +104,6 @@ namespace anm2ed glDeleteFramebuffers(1, &fbo); glDeleteRenderbuffers(1, &rbo); glDeleteTextures(1, &texture); - glDeleteVertexArrays(1, &axisVAO); glDeleteBuffers(1, &axisVBO); @@ -115,10 +114,7 @@ namespace anm2ed glDeleteBuffers(1, &rectVBO); } - bool Canvas::is_valid() const - { - return fbo != 0; - } + bool Canvas::is_valid() const { return fbo != 0; } void Canvas::framebuffer_set() const { @@ -229,6 +225,8 @@ namespace anm2ed glBindBuffer(GL_ARRAY_BUFFER, 0); glBindTexture(GL_TEXTURE_2D, 0); glUseProgram(0); + + glEnable(GL_BLEND); } void Canvas::rect_render(Shader& shader, const mat4& transform, const mat4& model, vec4 color, float dashLength, @@ -267,25 +265,40 @@ namespace anm2ed glUseProgram(0); } - void Canvas::viewport_set() const - { - glViewport(0, 0, size.x, size.y); - } + void Canvas::viewport_set() const { glViewport(0, 0, size.x, size.y); } void Canvas::clear(const vec4& color) const { + glDisable(GL_BLEND); glClearColor(color.r, color.g, color.b, color.a); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_BLEND); } void Canvas::bind() const { glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + GLboolean blendEnabled = glIsEnabled(GL_BLEND); + if (!blendEnabled) glEnable(GL_BLEND); + + glGetIntegerv(GL_BLEND_SRC_RGB, &previousSrcRGB); + glGetIntegerv(GL_BLEND_DST_RGB, &previousDstRGB); + glGetIntegerv(GL_BLEND_SRC_ALPHA, &previousSrcAlpha); + glGetIntegerv(GL_BLEND_DST_ALPHA, &previousDstAlpha); + previousBlendStored = true; + + glBlendFuncSeparate(GL_ONE, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); } void Canvas::unbind() const { glBindFramebuffer(GL_FRAMEBUFFER, 0); + if (previousBlendStored) + { + glBlendFuncSeparate(previousSrcRGB, previousDstRGB, previousSrcAlpha, previousDstAlpha); + previousBlendStored = false; + } } std::vector Canvas::pixels_get() const diff --git a/src/canvas.h b/src/canvas.h index e013017..e29c063 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -46,6 +46,11 @@ namespace anm2ed GLuint texture{}; glm::vec2 previousSize{}; glm::vec2 size{}; + mutable GLint previousSrcRGB{}; + mutable GLint previousDstRGB{}; + mutable GLint previousSrcAlpha{}; + mutable GLint previousDstAlpha{}; + mutable bool previousBlendStored{}; Canvas(); Canvas(glm::vec2); diff --git a/src/imgui/taskbar.cpp b/src/imgui/taskbar.cpp index 420a624..d2199a3 100644 --- a/src/imgui/taskbar.cpp +++ b/src/imgui/taskbar.cpp @@ -5,7 +5,9 @@ #include #include #include +#include #include +#include #include #include @@ -16,7 +18,6 @@ #include "types.h" #include "icon.h" -#include "toast.h" using namespace anm2ed::resource; using namespace anm2ed::types; @@ -26,6 +27,107 @@ using namespace glm; namespace anm2ed::imgui { + static constexpr auto ANM2ED_LABEL = "Anm2Ed"; + static constexpr auto VERSION_LABEL = "Version 2.0"; + static constexpr auto CREDIT_DELAY = 1.0f; + static constexpr auto CREDIT_SCROLL_SPEED = 25.0f; + + struct Credit + { + const char* string{}; + font::Type font{font::REGULAR}; + }; + + struct ScrollingCredit + { + int index{}; + float offset{}; + }; + + struct CreditsState + { + std::vector active{}; + float spawnTimer{1.0f}; + int nextIndex{}; + }; + + static constexpr Credit CREDITS[] = { + {"Anm2Ed", font::BOLD}, + {"License: GPLv3"}, + {""}, + {"Designer", font::BOLD}, + {"Shweet"}, + {""}, + {"Additional Help", font::BOLD}, + {"im-tem"}, + {""}, + {"Based on the work of:", font::BOLD}, + {"Adrian Gavrilita"}, + {"Simon Parzer"}, + {"Matt Kapuszczak"}, + {""}, + {"XM Music", font::BOLD}, + {"Drozerix"}, + {"\"Keygen Wraith\""}, + {"https://modarchive.org/module.php?207854"}, + {"License: CC0"}, + {""}, + {"Libraries", font::BOLD}, + {"Dear ImGui"}, + {"https://github.com/ocornut/imgui"}, + {"License: MIT"}, + {""}, + {"SDL"}, + {"https://github.com/libsdl-org/SDL"}, + {"License: zlib"}, + {""}, + {"SDL_mixer"}, + {"https://github.com/libsdl-org/SDL_mixer"}, + {"License: zlib"}, + {""}, + {"tinyxml2"}, + {"https://github.com/leethomason/tinyxml2"}, + {"License: zlib"}, + {""}, + {"glm"}, + {"https://github.com/g-truc/glm"}, + {"License: MIT"}, + {""}, + {"lunasvg"}, + {"https://github.com/sammycage/lunasvg"}, + {"License: MIT"}, + {""}, + {"Icons", font::BOLD}, + {"Remix Icons"}, + {"remixicon.com"}, + {"License: Apache"}, + {""}, + {"Font", font::BOLD}, + {"Noto Sans"}, + {"https://fonts.google.com/noto/specimen/Noto+Sans"}, + {"License: OFL"}, + {""}, + {"Special Thanks", font::BOLD}, + {"Edmund McMillen"}, + {"Florian Himsl"}, + {"Tyrone Rodriguez"}, + {"The-Vinh Truong (_kilburn)"}, + {"Everyone who waited patiently for this to be finished"}, + {"Everyone else who has worked on The Binding of Isaac!"}, + {""}, + {""}, + {""}, + {""}, + {""}, + {"enjoy the jams :)"}, + {""}, + {""}, + {""}, + {""}, + {""}, + }; + static constexpr auto CREDIT_COUNT = (int)(sizeof(CREDITS) / sizeof(Credit)); + Taskbar::Taskbar() : generate(vec2()) {} void Taskbar::update(Manager& manager, Settings& settings, Resources& resources, Dialog& dialog, bool& isQuitting) @@ -43,9 +145,10 @@ namespace anm2ed::imgui if (ImGui::MenuItem("New", settings.shortcutNew.c_str())) dialog.file_save(dialog::ANM2_NEW); if (ImGui::MenuItem("Open", settings.shortcutOpen.c_str())) dialog.file_open(dialog::ANM2_OPEN); - if (ImGui::BeginMenu("Open Recent", !manager.recentFiles.empty())) + auto recentFiles = manager.recent_files_ordered(); + if (ImGui::BeginMenu("Open Recent", !recentFiles.empty())) { - for (auto [i, file] : std::views::enumerate(manager.recentFiles)) + for (auto [i, file] : std::views::enumerate(recentFiles)) { auto label = std::format(FILE_LABEL_FORMAT, file.filename().string(), file.string()); @@ -54,7 +157,7 @@ namespace anm2ed::imgui ImGui::PopID(); } - if (!manager.recentFiles.empty()) + if (!recentFiles.empty()) if (ImGui::MenuItem("Clear List")) manager.recent_files_clear(); ImGui::EndMenu(); @@ -407,7 +510,6 @@ namespace anm2ed::imgui if (ImGui::BeginPopupModal(renderPopup.label, &renderPopup.isOpen, ImGuiWindowFlags_NoResize)) { - auto& playback = document->playback; auto& ffmpegPath = settings.renderFFmpegPath; auto& path = settings.renderPath; auto& format = settings.renderFormat; @@ -416,27 +518,88 @@ namespace anm2ed::imgui auto& type = settings.renderType; auto& start = manager.recordingStart; auto& end = manager.recordingEnd; + auto& rows = settings.renderRows; + auto& columns = settings.renderColumns; auto& isRange = manager.isRecordingRange; - auto widgetSize = widget_size_with_row_get(2); - auto dialogType = type == render::PNGS ? dialog::PNG_DIRECTORY_SET - : type == render::GIF ? dialog::GIF_PATH_SET - : type == render::WEBM ? dialog::WEBM_PATH_SET - : dialog::NONE; + auto& frames = document->frames.selection; + int length = std::max(1, end - start + 1); + + auto range_set = [&]() + { + if (!frames.empty()) + { + if (auto item = document->item_get()) + { + int duration{}; + for (auto [i, frame] : std::views::enumerate(item->frames)) + { + if (i == *frames.begin()) + start = duration; + else if (i == *frames.rbegin()) + { + end = duration; + break; + } + + duration += frame.duration; + } + } + } + else if (!isRange) + { + start = 0; + end = animation->frameNum - 1; + } + + length = std::max(1, end - start + 1); + }; + + auto rows_columns_set = [&]() + { + auto framesNeeded = std::max(1, length); + int bestRows = 1; + int bestColumns = framesNeeded; + + auto bestScore = std::make_tuple(bestColumns - bestRows, bestColumns * bestRows - framesNeeded, -bestColumns); + + for (int candidateRows = 1; candidateRows <= framesNeeded; ++candidateRows) + { + int candidateColumns = (framesNeeded + candidateRows - 1) / candidateRows; + if (candidateColumns < candidateRows) break; + + auto candidateScore = std::make_tuple(candidateColumns - candidateRows, + candidateColumns * candidateRows - framesNeeded, -candidateColumns); + + if (candidateScore < bestScore) + { + bestScore = candidateScore; + bestRows = candidateRows; + bestColumns = candidateColumns; + } + } + + rows = bestRows; + columns = bestColumns; + }; auto replace_extension = [&]() { path = std::filesystem::path(path).replace_extension(render::EXTENSIONS[type]).string(); }; - auto range_to_length = [&]() - { - start = 0; - end = animation->frameNum; - }; - - if (renderPopup.isJustOpened) + auto render_set = [&]() { replace_extension(); - if (!isRange) range_to_length(); - } + range_set(); + rows_columns_set(); + }; + + auto widgetSize = widget_size_with_row_get(2); + auto dialogType = type == render::PNGS ? dialog::PNG_DIRECTORY_SET + : type == render::SPRITESHEET ? dialog::PNG_PATH_SET + : type == render::GIF ? dialog::GIF_PATH_SET + : type == render::WEBM ? dialog::WEBM_PATH_SET + : dialog::NONE; + + if (renderPopup.isJustOpened) render_set(); if (ImGui::ImageButton("##FFmpeg Path Set", resources.icons[icon::FOLDER].id, icon_size_get())) dialog.file_open(dialog::FFMPEG_PATH_SET); @@ -458,41 +621,64 @@ namespace anm2ed::imgui ImGui::SetItemTooltip("Set the output path or directory for the animation."); dialog.set_string_to_selected_path(path, dialogType); - if (ImGui::Combo("Type", &type, render::STRINGS, render::COUNT)) replace_extension(); + if (ImGui::Combo("Type", &type, render::STRINGS, render::COUNT)) render_set(); ImGui::SetItemTooltip("Set the type of the output."); + if (type == render::PNGS || type == render::SPRITESHEET) ImGui::Separator(); + if (type == render::PNGS) { - ImGui::Separator(); - input_text_string("Format", &format); + if (input_text_string("Format", &format)) format = std::filesystem::path(format).replace_extension(".png"); ImGui::SetItemTooltip( "For outputted images, each image will use this format.\n{} represents the index of each image."); } + else if (type == render::SPRITESHEET) + { + input_int_range("Rows", rows, 1, length); + ImGui::SetItemTooltip("Set how many rows the spritesheet will have."); + + input_int_range("Columns", columns, 1, length); + ImGui::SetItemTooltip("Set how many columns the spritesheet will have."); + + if (ImGui::Button("Set to Recommended")) rows_columns_set(); + ImGui::SetItemTooltip("Use a recommended value for rows/columns."); + } ImGui::Separator(); if (ImGui::Checkbox("Custom Range", &isRange)) - if (!isRange) range_to_length(); - ImGui::SetItemTooltip("Toggle using a custom range for the animation."); + { + range_set(); + ImGui::SetItemTooltip("Toggle using a custom range for the animation."); + } - if (isRange) + ImGui::SameLine(); + + ImGui::BeginDisabled(frames.empty()); + if (ImGui::Button("To Selected Frames")) range_set(); + ImGui::SetItemTooltip("If frames are selected, use that range for the rendered animation."); + ImGui::EndDisabled(); + + ImGui::BeginDisabled(!isRange); { input_int_range("Start", start, 0, animation->frameNum - 1); - ImGui::SetItemTooltip("Set the starting time of the animation."); + ImGui::SetItemTooltip("Set the starting time of the animation."); input_int_range("End", end, start + 1, animation->frameNum); - ImGui::SetItemTooltip("Set the ending time of the animation."); + ImGui::SetItemTooltip("Set the ending time of the animation."); } + ImGui::EndDisabled(); ImGui::Separator(); ImGui::Checkbox("Raw", &isRaw); ImGui::SetItemTooltip("Record only the raw animation; i.e., only its layers, to its bounds."); - if (isRaw) + ImGui::BeginDisabled(!isRaw); { input_float_range("Scale", scale, 1.0f, 100.0f, STEP, STEP_FAST, "%.1fx"); ImGui::SetItemTooltip("Set the output scale of the animation."); } + ImGui::EndDisabled(); ImGui::Separator(); @@ -504,78 +690,11 @@ namespace anm2ed::imgui if (ImGui::Button("Render", widgetSize)) { - bool canStart = true; - auto warn_and_close = [&](const std::string& message) - { - toasts.warning(message); - renderPopup.close(); - canStart = false; - }; - - auto ffmpegPathValid = [&]() -> bool - { - if (ffmpegPath.empty()) return false; - std::error_code ec{}; - std::filesystem::path ffmpeg(ffmpegPath); - if (!std::filesystem::exists(ffmpeg, ec) || !std::filesystem::is_regular_file(ffmpeg, ec)) return false; -#ifdef _WIN32 - auto ext = ffmpeg.extension().string(); - std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c) { return (char)std::tolower(c); }); - if (ext != ".exe") return false; - return true; -#else - auto permMask = std::filesystem::status(ffmpeg, ec).permissions(); - using std::filesystem::perms; - if (permMask == perms::unknown) return true; - auto has_exec = [&](perms p) - { - return (perms::none != (p & perms::owner_exec)) || (perms::none != (p & perms::group_exec)) || - (perms::none != (p & perms::others_exec)); - }; - return has_exec(permMask); -#endif - }; - - if (!ffmpegPathValid()) warn_and_close("Invalid FFmpeg executable. Please set a valid FFmpeg path."); - - if (canStart) - { - std::error_code ec{}; - if (type == render::PNGS) - { - if (path.empty()) - warn_and_close("Select an output directory for PNG exports."); - else - { - std::filesystem::path directory(path); - if (!std::filesystem::exists(directory, ec) || !std::filesystem::is_directory(directory, ec)) - warn_and_close("PNG exports require a valid directory."); - } - } - else - { - std::filesystem::path output(path); - auto parent = output.parent_path(); - auto parentInvalid = - !parent.empty() && (!std::filesystem::exists(parent, ec) || !std::filesystem::is_directory(parent, ec)); - if (path.empty() || std::filesystem::is_directory(output, ec) || parentInvalid) - { - output = std::filesystem::path("output").replace_extension(render::EXTENSIONS[type]); - path = output.string(); - warn_and_close(std::format("Invalid output file. Using default path: {}", path)); - } - } - } - - if (canStart) - { - manager.isRecordingStart = true; - playback.time = start; - playback.isPlaying = true; - renderPopup.close(); - manager.progressPopup.open(); - } + manager.isRecordingStart = true; + renderPopup.close(); + manager.progressPopup.open(); } + ImGui::SetItemTooltip("Render the animation using the current settings."); ImGui::SameLine(); @@ -590,107 +709,6 @@ namespace anm2ed::imgui if (ImGui::BeginPopupModal(aboutPopup.label, &aboutPopup.isOpen, ImGuiWindowFlags_NoResize)) { - struct Credit - { - const char* string{}; - font::Type font{font::REGULAR}; - }; - - struct ScrollingCredit - { - int index{}; - float offset{}; - }; - - struct CreditsState - { - std::vector active{}; - float spawnTimer{1.0f}; - int nextIndex{}; - }; - - static constexpr auto ANM2ED_LABEL = "Anm2Ed"; - static constexpr auto VERSION_LABEL = "Version 2.0"; - static constexpr auto CREDIT_DELAY = 1.0f; - static constexpr auto CREDIT_SCROLL_SPEED = 25.0f; - - static constexpr Credit CREDITS[] = { - {"Anm2Ed", font::BOLD}, - {"License: GPLv3"}, - {""}, - {"Designer", font::BOLD}, - {"Shweet"}, - {""}, - {"Additional Help", font::BOLD}, - {"im-tem"}, - {""}, - {"Based on the work of:", font::BOLD}, - {"Adrian Gavrilita"}, - {"Simon Parzer"}, - {"Matt Kapuszczak"}, - {""}, - {"XM Music", font::BOLD}, - {"Drozerix"}, - {"\"Keygen Wraith\""}, - {"https://modarchive.org/module.php?207854"}, - {"License: CC0"}, - {""}, - {"Libraries", font::BOLD}, - {"Dear ImGui"}, - {"https://github.com/ocornut/imgui"}, - {"License: MIT"}, - {""}, - {"SDL"}, - {"https://github.com/libsdl-org/SDL"}, - {"License: zlib"}, - {""}, - {"SDL_mixer"}, - {"https://github.com/libsdl-org/SDL_mixer"}, - {"License: zlib"}, - {""}, - {"tinyxml2"}, - {"https://github.com/leethomason/tinyxml2"}, - {"License: zlib"}, - {""}, - {"glm"}, - {"https://github.com/g-truc/glm"}, - {"License: MIT"}, - {""}, - {"lunasvg"}, - {"https://github.com/sammycage/lunasvg"}, - {"License: MIT"}, - {""}, - {"Icons", font::BOLD}, - {"Remix Icons"}, - {"remixicon.com"}, - {"License: Apache"}, - {""}, - {"Font", font::BOLD}, - {"Noto Sans"}, - {"https://fonts.google.com/noto/specimen/Noto+Sans"}, - {"License: OFL"}, - {""}, - {"Special Thanks", font::BOLD}, - {"Edmund McMillen"}, - {"Florian Himsl"}, - {"Tyrone Rodriguez"}, - {"The-Vinh Truong (_kilburn)"}, - {"Everyone who waited patiently for this to be finished"}, - {"Everyone else who has worked on The Binding of Isaac!"}, - {""}, - {""}, - {""}, - {""}, - {""}, - {"enjoy the jams :)"}, - {""}, - {""}, - {""}, - {""}, - {""}, - }; - static constexpr auto CREDIT_COUNT = (int)(sizeof(CREDITS) / sizeof(Credit)); - static CreditsState creditsState{}; auto credits_reset = [&]() diff --git a/src/imgui/window/animation_preview.cpp b/src/imgui/window/animation_preview.cpp index f20d545..23b2a93 100644 --- a/src/imgui/window/animation_preview.cpp +++ b/src/imgui/window/animation_preview.cpp @@ -1,5 +1,7 @@ #include "animation_preview.h" +#include +#include #include #include @@ -14,6 +16,7 @@ using namespace anm2ed::canvas; using namespace anm2ed::types; using namespace anm2ed::util; using namespace anm2ed::resource; +using namespace anm2ed::resource::texture; using namespace glm; namespace anm2ed::imgui @@ -26,47 +29,27 @@ namespace anm2ed::imgui AnimationPreview::AnimationPreview() : Canvas(vec2()) {} - void AnimationPreview::tick(Manager& manager, Document& document, Settings& settings) + void AnimationPreview::tick(Manager& manager, Settings& settings) { + auto& document = *manager.get(); auto& anm2 = document.anm2; auto& playback = document.playback; auto& frameTime = document.frameTime; + auto& end = manager.recordingEnd; auto& zoom = document.previewZoom; auto& pan = document.previewPan; - auto& isRootTransform = settings.previewIsRootTransform; - auto& scale = settings.renderScale; - - if (playback.isPlaying) - { - auto& isSound = settings.timelineIsSound; - auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers; - - if (!anm2.content.sounds.empty() && isSound) - { - if (auto animation = document.animation_get(); - animation && animation->triggers.isVisible && (!isOnlyShowLayers || manager.isRecording)) - { - if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER); - trigger.is_visible(anm2::TRIGGER)) - if (anm2.content.sounds.contains(trigger.soundID)) - anm2.content.sounds[trigger.soundID].audio.play(false, mixer); - } - } - - frameTime = playback.time; - } if (manager.isRecording) { + auto& ffmpegPath = settings.renderFFmpegPath; + auto& path = settings.renderPath; + auto& type = settings.renderType; + auto pixels = pixels_get(); renderFrames.push_back(Texture(pixels.data(), size)); - if (playback.time > manager.recordingEnd || playback.isFinished) + if (playback.time > end || playback.isFinished) { - auto& ffmpegPath = settings.renderFFmpegPath; - auto& path = settings.renderPath; - auto& type = settings.renderType; - if (type == render::PNGS) { auto& format = settings.renderFormat; @@ -89,6 +72,53 @@ namespace anm2ed::imgui else toasts.warning(std::format("Could not export frames to: {}", path)); } + else if (type == render::SPRITESHEET) + { + auto& rows = settings.renderRows; + auto& columns = settings.renderColumns; + + if (renderFrames.empty()) + { + toasts.warning("No frames captured for spritesheet export."); + } + else + { + const auto& firstFrame = renderFrames.front(); + if (firstFrame.size.x <= 0 || firstFrame.size.y <= 0 || firstFrame.pixels.empty()) + toasts.warning("Spritesheet export failed: captured frames are empty."); + else + { + auto frameWidth = firstFrame.size.x; + auto frameHeight = firstFrame.size.y; + ivec2 spritesheetSize = ivec2(frameWidth * columns, frameHeight * rows); + + std::vector spritesheet((size_t)(spritesheetSize.x) * spritesheetSize.y * CHANNELS); + + for (auto [i, frame] : std::views::enumerate(renderFrames)) + { + auto row = (int)(i / columns); + auto column = (int)(i % columns); + if (row >= rows || column >= columns) break; + if ((int)frame.pixels.size() < frameWidth * frameHeight * CHANNELS) continue; + + for (int y = 0; y < frameHeight; ++y) + { + auto destY = (size_t)(row * frameHeight + y); + auto destX = (size_t)(column * frameWidth); + auto destOffset = (destY * spritesheetSize.x + destX) * CHANNELS; + auto srcOffset = (size_t)(y * frameWidth) * CHANNELS; + std::copy_n(frame.pixels.data() + srcOffset, frameWidth * CHANNELS, spritesheet.data() + destOffset); + } + } + + Texture spritesheetTexture(spritesheet.data(), spritesheetSize); + if (spritesheetTexture.write_png(path)) + toasts.info(std::format("Exported spritesheet to: {}", path)); + else + toasts.warning(std::format("Could not export spritesheet to: {}", path)); + } + } + } else { if (animation_render(ffmpegPath, path, renderFrames, audioStream, (render::Type)type, size, anm2.info.fps)) @@ -112,41 +142,29 @@ namespace anm2ed::imgui manager.progressPopup.close(); } } - if (manager.isRecordingStart) + + if (playback.isPlaying) { - savedSettings = settings; + auto animation = document.animation_get(); + auto& isSound = settings.timelineIsSound; + auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers; - if (settings.timelineIsSound) audioStream.capture_begin(mixer); - - if (settings.renderIsRawAnimation) + if (!anm2.content.sounds.empty() && isSound) { - settings.previewBackgroundColor = vec4(); - settings.previewIsGrid = false; - settings.previewIsAxes = false; - settings.timelineIsOnlyShowLayers = true; - settings.onionskinIsEnabled = false; - - savedZoom = zoom; - savedPan = pan; - - if (auto animation = document.animation_get()) + if (auto animation = document.animation_get(); + animation && animation->triggers.isVisible && (!isOnlyShowLayers || manager.isRecording)) { - if (auto rect = animation->rect(isRootTransform); rect != vec4(-1.0f)) - { - size_set(vec2(rect.z, rect.w) * scale); - set_to_rect(zoom, pan, rect); - } + if (auto trigger = animation->triggers.frame_generate(playback.time, anm2::TRIGGER); + trigger.is_visible(anm2::TRIGGER)) + if (anm2.content.sounds.contains(trigger.soundID)) + anm2.content.sounds[trigger.soundID].audio.play(false, mixer); } - - isSizeTrySet = false; - - bind(); - clear(settings.previewBackgroundColor); - unbind(); } - manager.isRecordingStart = false; - manager.isRecording = true; + playback.tick(anm2.info.fps, animation->frameNum, + (animation->isLoop || settings.playbackIsLoop) && !manager.isRecording); + + frameTime = playback.time; } } @@ -278,6 +296,38 @@ namespace anm2ed::imgui auto cursorScreenPos = ImGui::GetCursorScreenPos(); + if (manager.isRecordingStart) + { + savedSettings = settings; + + if (settings.timelineIsSound) audioStream.capture_begin(mixer); + + if (settings.renderIsRawAnimation) + { + settings.previewBackgroundColor = vec4(); + settings.previewIsGrid = false; + settings.previewIsAxes = false; + settings.timelineIsOnlyShowLayers = true; + settings.onionskinIsEnabled = false; + + savedZoom = zoom; + savedPan = pan; + + if (auto rect = document.animation_get()->rect(isRootTransform); rect != vec4(-1.0f)) + { + size_set(vec2(rect.z, rect.w) * settings.renderScale); + set_to_rect(zoom, pan, rect); + } + + isSizeTrySet = false; + } + + manager.isRecordingStart = false; + manager.isRecording = true; + playback.isPlaying = true; + playback.time = manager.recordingStart; + } + if (isSizeTrySet) size_set(to_vec2(ImGui::GetContentRegionAvail())); viewport_set(); bind(); diff --git a/src/loader.cpp b/src/loader.cpp index 0284249..d5d08af 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -151,7 +151,6 @@ namespace anm2ed glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glLineWidth(2.0f); - glDisable(GL_MULTISAMPLE); glDisable(GL_DEPTH_TEST); glDisable(GL_LINE_SMOOTH); glClearColor(color::BLACK.r, color::BLACK.g, color::BLACK.b, color::BLACK.a);