Files
anm2ed/src/animation_preview.cpp

390 lines
16 KiB
C++

#include "animation_preview.h"
#include <glm/gtc/type_ptr.hpp>
#include "imgui.h"
#include "math.h"
#include "tool.h"
#include "types.h"
using namespace anm2ed::manager;
using namespace anm2ed::settings;
using namespace anm2ed::canvas;
using namespace anm2ed::playback;
using namespace anm2ed::resources;
using namespace anm2ed::types;
using namespace glm;
namespace anm2ed::animation_preview
{
constexpr auto NULL_COLOR = vec4(0.0f, 0.0f, 1.0f, 0.90f);
constexpr auto TARGET_SIZE = vec2(32, 32);
constexpr auto POINT_SIZE = vec2(4, 4);
constexpr auto NULL_RECT_SIZE = vec2(100);
constexpr auto TRIGGER_TEXT_COLOR = ImVec4(1.0f, 1.0f, 1.0f, 0.5f);
AnimationPreview::AnimationPreview() : Canvas(vec2())
{
}
void AnimationPreview::update(Manager& manager, Settings& settings, Resources& resources)
{
auto& document = *manager.get();
auto& anm2 = document.anm2;
auto& playback = document.playback;
auto& reference = document.reference;
auto animation = document.animation_get();
auto& pan = document.previewPan;
auto& zoom = document.previewZoom;
auto& backgroundColor = settings.previewBackgroundColor;
auto& axesColor = settings.previewAxesColor;
auto& gridColor = settings.previewGridColor;
auto& gridSize = settings.previewGridSize;
auto& gridOffset = settings.previewGridOffset;
auto& zoomStep = settings.viewZoomStep;
auto& isGrid = settings.previewIsGrid;
auto& overlayTransparency = settings.previewOverlayTransparency;
auto& overlayIndex = document.overlayIndex;
auto& isRootTransform = settings.previewIsRootTransform;
auto& isPivots = settings.previewIsPivots;
auto& isAxes = settings.previewIsAxes;
auto& isAltIcons = settings.previewIsAltIcons;
auto& isBorder = settings.previewIsBorder;
auto& tool = settings.tool;
auto& isOnlyShowLayers = settings.timelineIsOnlyShowLayers;
auto& shaderLine = resources.shaders[shader::LINE];
auto& shaderAxes = resources.shaders[shader::AXIS];
auto& shaderGrid = resources.shaders[shader::GRID];
auto& shaderTexture = resources.shaders[shader::TEXTURE];
settings.previewPan = pan;
settings.previewZoom = zoom;
if (ImGui::Begin("Animation Preview", &settings.windowIsAnimationPreview))
{
auto childSize = ImVec2(imgui::row_widget_width_get(4),
(ImGui::GetTextLineHeightWithSpacing() * 4) + (ImGui::GetStyle().WindowPadding.y * 2));
if (ImGui::BeginChild("##Grid Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
{
ImGui::Checkbox("Grid", &isGrid);
ImGui::SameLine();
ImGui::ColorEdit4("Color", value_ptr(gridColor), ImGuiColorEditFlags_NoInputs);
ImGui::InputInt2("Size", value_ptr(gridSize));
ImGui::InputInt2("Offset", value_ptr(gridOffset));
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##View Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
{
ImGui::InputFloat("Zoom", &zoom, zoomStep, zoomStep, "%.0f%%");
auto widgetSize = imgui::widget_size_with_row_get(2);
imgui::shortcut(settings.shortcutCenterView);
if (ImGui::Button("Center View", widgetSize)) pan = vec2();
imgui::set_item_tooltip_shortcut("Centers the view.", settings.shortcutCenterView);
ImGui::SameLine();
imgui::shortcut(settings.shortcutFit);
if (ImGui::Button("Fit", widgetSize))
if (animation) set_to_rect(zoom, pan, animation->rect(isRootTransform));
imgui::set_item_tooltip_shortcut("Set the view to match the extent of the animation.", settings.shortcutFit);
ImGui::TextUnformatted(std::format(POSITION_FORMAT, (int)mousePos.x, (int)mousePos.y).c_str());
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##Background Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
{
ImGui::ColorEdit4("Background", value_ptr(backgroundColor), ImGuiColorEditFlags_NoInputs);
ImGui::SameLine();
ImGui::Checkbox("Axes", &isAxes);
ImGui::SameLine();
ImGui::ColorEdit4("Color", value_ptr(axesColor), ImGuiColorEditFlags_NoInputs);
std::vector<std::string> animationNames{};
animationNames.emplace_back("None");
for (auto& animation : anm2.animations.items)
animationNames.emplace_back(animation.name);
imgui::combo_strings("Overlay", &overlayIndex, animationNames);
ImGui::InputFloat("Alpha", &overlayTransparency, 0, 0, "%.0f");
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##Helpers Child", childSize, true, ImGuiWindowFlags_HorizontalScrollbar))
{
auto helpersChildSize = ImVec2(imgui::row_widget_width_get(2), ImGui::GetContentRegionAvail().y);
if (ImGui::BeginChild("##Helpers Child 1", helpersChildSize))
{
ImGui::Checkbox("Root Transform", &isRootTransform);
ImGui::Checkbox("Pivots", &isPivots);
}
ImGui::EndChild();
ImGui::SameLine();
if (ImGui::BeginChild("##Helpers Child 2", helpersChildSize))
{
ImGui::Checkbox("Alt Icons", &isAltIcons);
ImGui::Checkbox("Border", &isBorder);
}
ImGui::EndChild();
}
ImGui::EndChild();
auto cursorScreenPos = ImGui::GetCursorScreenPos();
size_set(to_vec2(ImGui::GetContentRegionAvail()));
bind();
viewport_set();
clear(backgroundColor);
if (isAxes) axes_render(shaderAxes, zoom, pan, axesColor);
if (isGrid) grid_render(shaderGrid, zoom, pan, gridSize, gridOffset, gridColor);
auto render = [&](anm2::Animation* animation, float time, vec3 colorOffset = {}, float alphaOffset = {},
bool isOnionskin = false)
{
auto transform = transform_get(zoom, pan);
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);
if (!isOnlyShowLayers && root.isVisible && animation->rootAnimation.isVisible)
{
auto rootTransform = transform * math::quad_model_get(TARGET_SIZE, root.position, TARGET_SIZE * 0.5f,
math::percent_to_unit(root.scale), root.rotation);
vec4 color = isOnionskin ? vec4(colorOffset, alphaOffset) : color::GREEN;
texture_render(shaderTexture, resources.icons[icon::TARGET].id, rootTransform, color);
}
for (auto& id : animation->layerOrder)
{
auto& layerAnimation = animation->layerAnimations.at(id);
if (!layerAnimation.isVisible) continue;
auto& layer = anm2.content.layers.at(id);
if (auto frame = layerAnimation.frame_generate(time, anm2::LAYER); frame.isVisible)
{
auto spritesheet = anm2.spritesheet_get(layer.spritesheetID);
if (!spritesheet) continue;
auto& texture = spritesheet->texture;
if (!texture.is_valid()) continue;
auto layerModel = math::quad_model_get(frame.size, frame.position, frame.pivot,
math::percent_to_unit(frame.scale), frame.rotation);
auto layerTransform = transform * layerModel;
auto uvMin = frame.crop / vec2(texture.size);
auto uvMax = (frame.crop + frame.size) / vec2(texture.size);
auto vertices = math::uv_vertices_get(uvMin, uvMax);
vec3 frameColorOffset = frame.colorOffset + colorOffset;
vec4 frameTint = frame.tint;
frameTint.a = std::max(0.0f, frameTint.a - alphaOffset);
texture_render(shaderTexture, texture.id, layerTransform, frameTint, frameColorOffset, vertices.data());
auto color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset) : color::RED;
if (isBorder) rect_render(shaderLine, layerTransform, layerModel, color);
if (isPivots)
{
auto pivotModel = math::quad_model_get(PIVOT_SIZE, frame.position, PIVOT_SIZE * 0.5f,
math::percent_to_unit(frame.scale), frame.rotation);
auto pivotTransform = transform * pivotModel;
texture_render(shaderTexture, resources.icons[icon::PIVOT].id, pivotTransform, color);
}
}
}
for (auto& [id, nullAnimation] : animation->nullAnimations)
{
if (!nullAnimation.isVisible || isOnlyShowLayers) continue;
auto& isShowRect = anm2.content.nulls[id].isShowRect;
if (auto frame = nullAnimation.frame_generate(time, anm2::NULL_); frame.isVisible)
{
auto icon = isShowRect ? icon::POINT : icon::TARGET;
auto& size = isShowRect ? POINT_SIZE : TARGET_SIZE;
auto color = isOnionskin ? vec4(colorOffset, 1.0f - alphaOffset)
: id == reference.itemID && reference.itemType == anm2::NULL_ ? color::RED
: NULL_COLOR;
auto nullModel = math::quad_model_get(size, frame.position, size * 0.5f, math::percent_to_unit(frame.scale),
frame.rotation);
auto nullTransform = transform * nullModel;
texture_render(shaderTexture, resources.icons[icon].id, nullTransform, color);
if (isShowRect)
{
auto rectModel = math::quad_model_get(NULL_RECT_SIZE, frame.position, NULL_RECT_SIZE * 0.5f,
math::percent_to_unit(frame.scale), frame.rotation);
auto rectTransform = transform * rectModel;
rect_render(shaderLine, rectTransform, rectModel, color);
}
}
}
};
auto onionskin_render = [&](float time, int count, int direction, vec3 color)
{
for (int i = 1; i <= count; i++)
{
float useTime = time + (float)(direction * i);
float alphaOffset = (1.0f / (count + 1)) * i;
render(animation, useTime, color, alphaOffset, true);
}
};
auto onionskins_render = [&](float time)
{
onionskin_render(time, settings.onionskinBeforeCount, -1, settings.onionskinBeforeColor);
onionskin_render(time, settings.onionskinAfterCount, 1, settings.onionskinAfterColor);
};
auto frameTime = reference.frameTime > -1 && !playback.isPlaying ? reference.frameTime : playback.time;
if (animation)
{
auto& drawOrder = settings.onionskinDrawOrder;
auto& isEnabled = settings.onionskinIsEnabled;
if (drawOrder == draw_order::BELOW && isEnabled) onionskins_render(frameTime);
render(animation, frameTime);
if (overlayIndex > 0)
render(document.anm2.animation_get({overlayIndex - 1}), frameTime, {},
1.0f - math::uint8_to_float(overlayTransparency));
if (drawOrder == draw_order::ABOVE && isEnabled) onionskins_render(frameTime);
}
unbind();
ImGui::Image(texture, to_imvec2(size));
isPreviewHovered = ImGui::IsItemHovered();
if (animation && animation->triggers.isVisible)
{
if (auto trigger = animation->triggers.frame_generate(frameTime, anm2::TRIGGER); trigger.isVisible)
{
auto clipMin = ImGui::GetItemRectMin();
auto clipMax = ImGui::GetItemRectMax();
auto drawList = ImGui::GetWindowDrawList();
auto textPos = to_imvec2(to_vec2(cursorScreenPos) + to_vec2(ImGui::GetStyle().WindowPadding));
drawList->PushClipRect(clipMin, clipMax);
ImGui::PushFont(resources.fonts[font::BOLD].get(), font::SIZE_LARGE);
drawList->AddText(textPos, ImGui::GetColorU32(TRIGGER_TEXT_COLOR),
anm2.content.events.at(trigger.eventID).name.c_str());
ImGui::PopFont();
drawList->PopClipRect();
}
}
if (isPreviewHovered)
{
ImGui::SetKeyboardFocusHere(-1);
mousePos = position_translate(zoom, pan, to_vec2(ImGui::GetMousePos()) - to_vec2(cursorScreenPos));
auto isMouseClick = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
auto isMouseReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
auto isMouseDown = ImGui::IsMouseDown(ImGuiMouseButton_Left);
auto isMouseMiddleDown = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
auto isLeftPressed = ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false);
auto isRightPressed = ImGui::IsKeyPressed(ImGuiKey_RightArrow, false);
auto isUpPressed = ImGui::IsKeyPressed(ImGuiKey_UpArrow, false);
auto isDownPressed = ImGui::IsKeyPressed(ImGuiKey_DownArrow, false);
auto isLeftReleased = ImGui::IsKeyReleased(ImGuiKey_LeftArrow);
auto isRightReleased = ImGui::IsKeyReleased(ImGuiKey_RightArrow);
auto isUpReleased = ImGui::IsKeyReleased(ImGuiKey_UpArrow);
auto isDownReleased = ImGui::IsKeyReleased(ImGuiKey_DownArrow);
auto isLeft = imgui::chord_repeating(ImGuiKey_LeftArrow);
auto isRight = imgui::chord_repeating(ImGuiKey_RightArrow);
auto isUp = imgui::chord_repeating(ImGuiKey_UpArrow);
auto isDown = imgui::chord_repeating(ImGuiKey_DownArrow);
auto isMouseRightDown = ImGui::IsMouseDown(ImGuiMouseButton_Right);
auto mouseDelta = to_ivec2(ImGui::GetIO().MouseDelta);
auto mouseWheel = ImGui::GetIO().MouseWheel;
auto isZoomIn = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomIn));
auto isZoomOut = imgui::chord_repeating(imgui::string_to_chord(settings.shortcutZoomOut));
auto isMod = ImGui::IsKeyDown(ImGuiMod_Shift);
auto frame = document.frame_get();
auto useTool = tool;
auto step = isMod ? step::FAST : step::NORMAL;
auto isKeyPressed = isLeftPressed || isRightPressed || isUpPressed || isDownPressed;
auto isKeyReleased = isLeftReleased || isRightReleased || isUpReleased || isDownReleased;
auto isBegin = isMouseClick || isKeyPressed;
auto isEnd = isMouseReleased || isKeyReleased;
if (isMouseMiddleDown) useTool = tool::PAN;
if (tool == tool::MOVE && isMouseRightDown) useTool = tool::SCALE;
if (tool == tool::SCALE && isMouseRightDown) useTool = tool::MOVE;
switch (useTool)
{
case tool::PAN:
if (isMouseDown || isMouseMiddleDown) pan += mouseDelta;
break;
case tool::MOVE:
if (!frame) break;
if (isBegin) document.snapshot("Frame Position");
if (isMouseDown) frame->position = mousePos;
if (isLeft) frame->position.x -= step;
if (isRight) frame->position.x += step;
if (isUp) frame->position.y -= step;
if (isDown) frame->position.y += step;
if (isEnd) document.change(change::FRAMES);
break;
case tool::SCALE:
if (!frame) break;
if (isBegin) document.snapshot("Frame Scale");
if (isMouseDown) frame->scale += mouseDelta;
if (isLeft) frame->scale.x -= step;
if (isRight) frame->scale.x += step;
if (isUp) frame->scale.y -= step;
if (isDown) frame->scale.y += step;
if (isEnd) document.change(change::FRAMES);
break;
case tool::ROTATE:
if (!frame) break;
if (isBegin) document.snapshot("Frame Rotation");
if (isMouseDown) frame->rotation += mouseDelta.y;
if (isLeft || isDown) frame->rotation -= step;
if (isUp || isRight) frame->rotation += step;
if (isEnd) document.change(change::FRAMES);
break;
default:
break;
}
if (mouseWheel != 0 || isZoomIn || isZoomOut)
zoom_set(zoom, pan, vec2(mousePos), (mouseWheel > 0 || isZoomIn) ? zoomStep : -zoomStep);
}
}
ImGui::End();
}
}