From 6c6660d7c486c04a242bf77f2a88ff36f1926211 Mon Sep 17 00:00:00 2001 From: shweet Date: Wed, 19 Nov 2025 21:43:13 -0500 Subject: [PATCH] onionskin change --- src/anm2/item.cpp | 15 +++++ src/anm2/item.h | 3 +- src/imgui/window/animation_preview.cpp | 88 ++++++++++++++++++-------- src/imgui/window/onionskin.cpp | 7 ++ src/settings.h | 1 + src/types.h | 8 ++- 6 files changed, 93 insertions(+), 29 deletions(-) diff --git a/src/anm2/item.cpp b/src/anm2/item.cpp index 7e2d963..5c66a35 100644 --- a/src/anm2/item.cpp +++ b/src/anm2/item.cpp @@ -334,4 +334,19 @@ namespace anm2ed::anm2 return time; } + + int Item::frame_index_from_time_get(float time) + { + if (frames.empty()) return -1; + if (time <= 0.0f) return 0; + + float duration{}; + for (auto [i, frame] : std::views::enumerate(frames)) + { + duration += frame.duration; + if (time < duration) return (int)i; + } + + return (int)frames.size() - 1; + } } diff --git a/src/anm2/item.h b/src/anm2/item.h index 7855d5b..abbf131 100644 --- a/src/anm2/item.h +++ b/src/anm2/item.h @@ -26,6 +26,7 @@ namespace anm2ed::anm2 void frames_generate_from_grid(glm::ivec2, glm::ivec2, glm::ivec2, int, int, int); void frames_sort_by_at_frame(); int frame_index_from_at_frame_get(int); + int frame_index_from_time_get(float); float frame_time_from_index_get(int); }; -} \ No newline at end of file +} diff --git a/src/imgui/window/animation_preview.cpp b/src/imgui/window/animation_preview.cpp index 8afe7e0..7a629b3 100644 --- a/src/imgui/window/animation_preview.cpp +++ b/src/imgui/window/animation_preview.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -388,10 +389,9 @@ namespace anm2ed::imgui struct OnionskinSample { float time{}; + int indexOffset{}; vec3 colorOffset{}; float alphaOffset{}; - glm::mat4 transform{1.0f}; - anm2::Frame root{}; }; std::vector onionskinSamples; @@ -403,18 +403,13 @@ namespace anm2ed::imgui for (int i = 1; i <= count; ++i) { float useTime = frameTime + (float)(direction * i); - if (useTime < 0.0f || useTime > animation->frameNum) continue; float alphaOffset = (1.0f / (count + 1)) * i; OnionskinSample sample{}; sample.time = useTime; sample.colorOffset = color; sample.alphaOffset = alphaOffset; - sample.root = animation->rootAnimation.frame_generate(sample.time, anm2::ROOT); - sample.transform = baseTransform; - if (isRootTransform) - sample.transform *= math::quad_model_parent_get( - sample.root.position, {}, math::percent_to_unit(sample.root.scale), sample.root.rotation); + sample.indexOffset = direction * i; onionskinSamples.push_back(sample); } }; @@ -424,23 +419,48 @@ namespace anm2ed::imgui } auto render = [&](anm2::Animation* animation, float time, vec3 colorOffset = {}, float alphaOffset = {}, - const std::vector* layeredOnions = nullptr) + const std::vector* layeredOnions = nullptr, bool isIndexMode = false) { - auto transform = baseTransform; - auto root = animation->rootAnimation.frame_generate(time, anm2::ROOT); - - if (isRootTransform) - transform *= math::quad_model_parent_get(root.position, {}, math::percent_to_unit(root.scale), root.rotation); - - auto draw_root = [&](const anm2::Frame& rootFrame, vec3 sampleColor, float sampleAlpha, bool isOnion) + auto sample_time_for_item = [&](anm2::Item& item, const OnionskinSample& sample) -> std::optional { + if (!isIndexMode) + { + if (sample.time < 0.0f || sample.time > animation->frameNum) return std::nullopt; + return sample.time; + } + if (item.frames.empty()) return std::nullopt; + int baseIndex = item.frame_index_from_time_get(frameTime); + if (baseIndex < 0) return std::nullopt; + int sampleIndex = baseIndex + sample.indexOffset; + if (sampleIndex < 0 || sampleIndex >= (int)item.frames.size()) return std::nullopt; + return item.frame_time_from_index_get(sampleIndex); + }; + + auto transform_for_time = [&](anm2::Animation* anim, float t) + { + auto sampleTransform = baseTransform; + if (isRootTransform) + { + auto rootFrame = anim->rootAnimation.frame_generate(t, anm2::ROOT); + sampleTransform *= math::quad_model_parent_get(rootFrame.position, {}, + math::percent_to_unit(rootFrame.scale), rootFrame.rotation); + } + return sampleTransform; + }; + + auto transform = transform_for_time(animation, time); + + auto draw_root = + [&](float sampleTime, const glm::mat4& sampleTransform, vec3 sampleColor, float sampleAlpha, bool isOnion) + { + auto rootFrame = animation->rootAnimation.frame_generate(sampleTime, anm2::ROOT); if (isOnlyShowLayers || !rootFrame.isVisible || !animation->rootAnimation.isVisible) return; - auto rootTransform = - isRootTransform - ? baseTransform * math::quad_model_get(TARGET_SIZE, rootFrame.position, TARGET_SIZE * 0.5f, - math::percent_to_unit(rootFrame.scale), rootFrame.rotation) - : baseTransform * math::quad_model_get(TARGET_SIZE, {}, TARGET_SIZE * 0.5f); + auto rootModel = isRootTransform + ? math::quad_model_get(TARGET_SIZE, {}, TARGET_SIZE * 0.5f) + : math::quad_model_get(TARGET_SIZE, rootFrame.position, TARGET_SIZE * 0.5f, + math::percent_to_unit(rootFrame.scale), rootFrame.rotation); + auto rootTransform = sampleTransform * rootModel; vec4 color = isOnion ? vec4(sampleColor, sampleAlpha) : color::GREEN; @@ -450,9 +470,13 @@ namespace anm2ed::imgui if (layeredOnions) for (auto& sample : *layeredOnions) - draw_root(sample.root, sample.colorOffset, sample.alphaOffset, true); + if (auto sampleTime = sample_time_for_item(animation->rootAnimation, sample)) + { + auto sampleTransform = transform_for_time(animation, *sampleTime); + draw_root(*sampleTime, sampleTransform, sample.colorOffset, sample.alphaOffset, true); + } - draw_root(root, {}, 0.0f, false); + draw_root(time, transform, {}, 0.0f, false); for (auto& id : animation->layerOrder) { @@ -508,7 +532,11 @@ namespace anm2ed::imgui if (layeredOnions) for (auto& sample : *layeredOnions) - draw_layer(sample.time, sample.transform, sample.colorOffset, sample.alphaOffset, true); + if (auto sampleTime = sample_time_for_item(layerAnimation, sample)) + { + auto sampleTransform = transform_for_time(animation, *sampleTime); + draw_layer(*sampleTime, sampleTransform, sample.colorOffset, sample.alphaOffset, true); + } draw_layer(time, transform, {}, 0.0f, false); } @@ -550,7 +578,11 @@ namespace anm2ed::imgui if (layeredOnions) for (auto& sample : *layeredOnions) - draw_null(sample.time, sample.transform, sample.colorOffset, sample.alphaOffset, true); + if (auto sampleTime = sample_time_for_item(nullAnimation, sample)) + { + auto sampleTransform = transform_for_time(animation, *sampleTime); + draw_null(*sampleTime, sampleTransform, sample.colorOffset, sample.alphaOffset, true); + } draw_null(time, transform, {}, 0.0f, false); } @@ -560,10 +592,12 @@ namespace anm2ed::imgui { auto layeredOnions = settings.onionskinIsEnabled ? &onionskinSamples : nullptr; - render(animation, frameTime, {}, 0.0f, layeredOnions); + render(animation, frameTime, {}, 0.0f, layeredOnions, + settings.onionskinMode == static_cast(OnionskinMode::INDEX)); if (auto overlayAnimation = anm2.animation_get(overlayIndex)) - render(overlayAnimation, frameTime, {}, 1.0f - math::uint8_to_float(overlayTransparency)); + render(overlayAnimation, frameTime, {}, 1.0f - math::uint8_to_float(overlayTransparency), layeredOnions, + settings.onionskinMode == static_cast(OnionskinMode::INDEX)); } unbind(); diff --git a/src/imgui/window/onionskin.cpp b/src/imgui/window/onionskin.cpp index ff7f876..4afd8b2 100644 --- a/src/imgui/window/onionskin.cpp +++ b/src/imgui/window/onionskin.cpp @@ -18,6 +18,7 @@ namespace anm2ed::imgui auto& beforeColor = settings.onionskinBeforeColor; auto& afterCount = settings.onionskinAfterCount; auto& afterColor = settings.onionskinAfterColor; + auto& mode = settings.onionskinMode; if (ImGui::Begin("Onionskin", &settings.windowIsOnionskin)) { @@ -37,6 +38,12 @@ namespace anm2ed::imgui configure_widgets("Before", beforeCount, beforeColor); configure_widgets("After", afterCount, afterColor); + ImGui::SeparatorText("Mode"); + ImGui::RadioButton("Time", &mode, (int)OnionskinMode::TIME); + ImGui::SetItemTooltip("The onionskinned frames will be based on frame time."); + ImGui::SameLine(); + ImGui::RadioButton("Index", &mode, (int)OnionskinMode::INDEX); + ImGui::SetItemTooltip("The onionskinned frames will be based on frame index."); } ImGui::End(); diff --git a/src/settings.h b/src/settings.h index 98df22a..f281934 100644 --- a/src/settings.h +++ b/src/settings.h @@ -142,6 +142,7 @@ namespace anm2ed X(ONIONSKIN_AFTER_COUNT, onionskinAfterCount, "Frames", INT, 0) \ X(ONIONSKIN_BEFORE_COLOR, onionskinBeforeColor, "Color", VEC3, types::color::RED) \ X(ONIONSKIN_AFTER_COLOR, onionskinAfterColor, "Color", VEC3, types::color::BLUE) \ + X(ONIONSKIN_MODE, onionskinMode, "Mode", INT, (int)types::OnionskinMode::TIME) \ \ X(TOOL, tool, "##Tool", INT, 0) \ X(TOOL_COLOR, toolColor, "##Color", VEC4, {1.0, 1.0, 1.0, 1.0}) \ diff --git a/src/types.h b/src/types.h index 1c6d891..2b1a7df 100644 --- a/src/types.h +++ b/src/types.h @@ -82,6 +82,12 @@ namespace anm2ed::types::color namespace anm2ed::types { + enum class OnionskinMode + { + TIME, + INDEX + }; + constexpr auto ID_NONE = -1; constexpr ImVec2 to_imvec2(const glm::vec2& v) noexcept { return {v.x, v.y}; } @@ -104,4 +110,4 @@ namespace anm2ed::types static T value{-1}; return value; } -} \ No newline at end of file +}