onionskin change

This commit is contained in:
2025-11-19 21:43:13 -05:00
parent 631c9c02fa
commit 6c6660d7c4
6 changed files with 93 additions and 29 deletions

View File

@@ -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;
}
}

View File

@@ -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);
};
}
}

View File

@@ -2,6 +2,7 @@
#include <algorithm>
#include <filesystem>
#include <optional>
#include <ranges>
#include <glm/gtc/type_ptr.hpp>
@@ -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<OnionskinSample> 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<OnionskinSample>* layeredOnions = nullptr)
const std::vector<OnionskinSample>* 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<float>
{
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<int>(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<int>(OnionskinMode::INDEX));
}
unbind();

View File

@@ -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();

View File

@@ -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}) \

View File

@@ -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;
}
}
}