diff --git a/Keishiki/PlugboardNodes.cpp b/Keishiki/PlugboardNodes.cpp index 1daa201..fdc54be 100644 --- a/Keishiki/PlugboardNodes.cpp +++ b/Keishiki/PlugboardNodes.cpp @@ -319,109 +319,6 @@ namespace K::PlugboardNodes { .fetch = { FetchAbsoluteValue } }; - constexpr f32 EvalInterpolation(f32 x, const InterpolationExtra& extra) { - switch (extra.interp) { - case K_I_InConstant: - return 0.0f; - case K_I_OutConstant: - return 1.0f; - case K_I_Linear: - return x; - case K_I_Step: - return bx::getEaseFunc(bx::Easing::Step)(x); - case K_I_SmoothStep: - return bx::getEaseFunc(bx::Easing::SmoothStep)(x); - case K_I_InQuadratic: - return bx::getEaseFunc(bx::Easing::InQuad)(x); - case K_I_OutQuadratic: - return bx::getEaseFunc(bx::Easing::OutQuad)(x); - case K_I_InOutQuadratic: - return bx::getEaseFunc(bx::Easing::InOutQuad)(x); - case K_I_OutInQuadratic: - return bx::getEaseFunc(bx::Easing::OutInQuad)(x); - case K_I_InCubic: - return bx::getEaseFunc(bx::Easing::InCubic)(x); - case K_I_OutCubic: - return bx::getEaseFunc(bx::Easing::OutCubic)(x); - case K_I_InOutCubic: - return bx::getEaseFunc(bx::Easing::InOutCubic)(x); - case K_I_OutInCubic: - return bx::getEaseFunc(bx::Easing::OutInCubic)(x); - case K_I_InQuartic: - return bx::getEaseFunc(bx::Easing::InQuart)(x); - case K_I_OutQuartic: - return bx::getEaseFunc(bx::Easing::OutQuart)(x); - case K_I_InOutQuartic: - return bx::getEaseFunc(bx::Easing::InOutQuart)(x); - case K_I_OutInQuartic: - return bx::getEaseFunc(bx::Easing::OutInQuart)(x); - case K_I_InQuintic: - return bx::getEaseFunc(bx::Easing::InQuint)(x); - case K_I_OutQuintic: - return bx::getEaseFunc(bx::Easing::OutInQuint)(x); - case K_I_InOutQuintic: - return bx::getEaseFunc(bx::Easing::InOutQuint)(x); - case K_I_OutInQuintic: - return bx::getEaseFunc(bx::Easing::OutInQuint)(x); - case K_I_InSine: - return bx::getEaseFunc(bx::Easing::InSine)(x); - case K_I_OutSine: - return bx::getEaseFunc(bx::Easing::OutSine)(x); - case K_I_InOutSine: - return bx::getEaseFunc(bx::Easing::InOutSine)(x); - case K_I_OutInSine: - return bx::getEaseFunc(bx::Easing::OutInSine)(x); - case K_I_InExponential: - return bx::getEaseFunc(bx::Easing::InExpo)(x); - case K_I_OutExponential: - return bx::getEaseFunc(bx::Easing::OutExpo)(x); - case K_I_InOutExponential: - return bx::getEaseFunc(bx::Easing::InOutExpo)(x); - case K_I_OutInExponential: - return bx::getEaseFunc(bx::Easing::OutInExpo)(x); - case K_I_InCircular: - return bx::getEaseFunc(bx::Easing::InCirc)(x); - case K_I_OutCircular: - return bx::getEaseFunc(bx::Easing::OutCirc)(x); - case K_I_InOutCircular: - return bx::getEaseFunc(bx::Easing::InOutCirc)(x); - case K_I_OutInCircular: - return bx::getEaseFunc(bx::Easing::OutInCirc)(x); - case K_I_InElastic: - return bx::getEaseFunc(bx::Easing::InElastic)(x); - case K_I_OutElastic: - return bx::getEaseFunc(bx::Easing::OutElastic)(x); - case K_I_InOutElastic: - return bx::getEaseFunc(bx::Easing::InOutElastic)(x); - case K_I_OutInElastic: - return bx::getEaseFunc(bx::Easing::OutInElastic)(x); - case K_I_InBack: - return bx::getEaseFunc(bx::Easing::InBack)(x); - case K_I_OutBack: - return bx::getEaseFunc(bx::Easing::OutBack)(x); - case K_I_InOutBack: - return bx::getEaseFunc(bx::Easing::InOutBack)(x); - case K_I_OutInBack: - return bx::getEaseFunc(bx::Easing::OutInBack)(x); - case K_I_InBounce: - return bx::getEaseFunc(bx::Easing::InBounce)(x); - case K_I_OutBounce: - return bx::getEaseFunc(bx::Easing::OutBounce)(x); - case K_I_InOutBounce: - return bx::getEaseFunc(bx::Easing::InOutBounce)(x); - case K_I_OutInBounce: - return bx::getEaseFunc(bx::Easing::OutInBounce)(x); - case K_I_CubicBezier: { - f32 p2x = extra.v[0], p2y = extra.v[1], p3x = extra.v[2], p3y = extra.v[3]; - f32 a = 0.0f, b = p2x, c = p3x, d = 1.0f; - f64 t = Graphics::GetCubicUniqueReal(-a+3.0f*b-3.0f*c+d, 3.0f*a-6.0f*b+3.0f*c, -3.0f*a+3.0f*b,a-x); - return static_cast(Graphics::CubicBezier(0.0f, p2y, p3y, 1.0f, t)); - } - default: - return {}; - } - } - PlugboardGraph::T_Map::type FetchInterpolation(const CompState& s, const PlugboardGraph::NodeInstance& n, Vector *show_nodes) { auto *e = std::any_cast(&n.extra); f32 x = GetNodeInputArg(s, n, 0, show_nodes); diff --git a/Keishiki/UI.cpp b/Keishiki/UI.cpp index 1ef884a..7ba1802 100644 --- a/Keishiki/UI.cpp +++ b/Keishiki/UI.cpp @@ -253,7 +253,7 @@ namespace K::UI { ImGui::TextUnformatted(n.node->in[in].name.c_str()); // Update link info - std::visit([&socket_link_pos, &dragging_on_socket, &source, &link_pos, &style](auto&& arg) { + std::visit([&socket_link_pos, &dragging_on_socket, &source, &link_pos](auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v) { if (ImGui::IsItemActive()) @@ -408,6 +408,8 @@ namespace K::UI { ImGui::End(); return; } + static bool show_curve_editor = false; + if (ImGui::Shortcut(ImGuiKey_Space)) TogglePlay(); @@ -446,7 +448,7 @@ namespace K::UI { AddTransformLayer(s, name); } ImGui::SameLine(); - ImGui::SetNextItemWidth(-100.0f); + ImGui::SetNextItemWidth(250.0f); ImGui::InputText("##LayerName", &name); ImGui::SameLine(); ImGui::BeginDisabled(no_selection); @@ -474,6 +476,10 @@ namespace K::UI { } ImGui::EndDisabled(); + ImGui::SameLine(); + + ImGui::Checkbox("Chain Editor", &show_curve_editor); + ImGuiIO& io = ImGui::GetIO(); ImGuiStyle style = ImGui::GetStyle(); ImVec2 avail = ImGui::GetContentRegionAvail(); @@ -553,13 +559,19 @@ namespace K::UI { view_left_old = view_left, view_right = 1.0f, view_right_old = view_right; - f32 view_amt, mouse_tl_x; + f32 view_amt = view_right - view_left, mouse_tl_x; + f32 fr_step; // Mouse Handlers // Drag playhead/pan + static bool tl_clear_selection_request = false; + bool tl_clear_selection_request_this_frame = tl_clear_selection_request; auto draw_tl_bg_drag_area = [&view_width, &style, &view_amt, &s, &mouse_tl_x](f32 h = 0.0f) { static f32 pan_x, view_left_old, view_right_old; ImGui::InvisibleButton("##TL_BG", ImVec2{ view_width + style.CellPadding.x * 2, h == 0.0f ? row_height + style.CellPadding.y : h}); + if (ImGui::IsItemClicked()) { + tl_clear_selection_request = true; + } if (ImGui::IsItemActive()) { s.current_frame = TimelineScreenViewToFrame(view_left, view_amt, view_width, std::clamp(mouse_tl_x, 0.0f, view_width), s.frame_max); } @@ -602,6 +614,7 @@ namespace K::UI { } }; + constexpr static f32 knob_width = 8.0f; if (ImGui::BeginTable("Layers", 5, ImGuiTableFlags_BordersInner | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY)) { ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible ImGui::TableSetupColumn("#", ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_IndentDisable); @@ -626,15 +639,14 @@ namespace K::UI { ImGui::TableSetColumnIndex(4); auto *window = ImGui::GetCurrentWindow(); - const static f32 knob_width = 8.0f; tl_init_pos = ImGui::GetCursorScreenPos(); view_width = ImGui::GetColumnWidth() - 10.0f, tl_width = view_width - knob_width, view_height = ImGui::TableGetHeaderRowHeight(), control_left = tl_init_pos.x, control_right = tl_init_pos.x + view_width; - view_amt = view_right - view_left; mouse_tl_x = io.MousePos.x - tl_init_pos.x; + fr_step = 1.0f / static_cast(s.frame_max + 1) * view_width / view_amt; ImGui::SetCursorScreenPos({tl_init_pos.x - knob_width / 2.0f + view_width * view_left, tl_init_pos.y}); ImGui::InvisibleButton("##TL_L", ImVec2{knob_width, view_height}); @@ -645,7 +657,7 @@ namespace K::UI { view_left_old = view_left; } if (ImGui::IsItemActive()) { - view_left = std::clamp(view_left_old + ImGui::GetMouseDragDelta().x / tl_width * view_amt, 0.0f, view_right - 2.0f / static_cast(s.frame_max)); + view_left = std::clamp(view_left_old + ImGui::GetMouseDragDelta().x / tl_width, 0.0f, view_right - 2.0f / static_cast(s.frame_max)); } ImGui::SameLine(); @@ -658,7 +670,7 @@ namespace K::UI { view_right_old = view_right; } if (ImGui::IsItemActive()) { - view_left = std::clamp(view_left_old + ImGui::GetMouseDragDelta().x / tl_width * view_amt, 0.0f, 1.0f - view_amt); + view_left = std::clamp(view_left_old + ImGui::GetMouseDragDelta().x / tl_width, 0.0f, 1.0f - view_amt); view_right = view_left + view_amt; } ImGui::SameLine(); @@ -670,7 +682,7 @@ namespace K::UI { view_right_old = view_right; } if (ImGui::IsItemActive()) { - view_right = std::clamp(view_right_old + ImGui::GetMouseDragDelta().x / tl_width * view_amt, view_left + 2.0f / static_cast(s.frame_max), 1.0f); + view_right = std::clamp(view_right_old + ImGui::GetMouseDragDelta().x / tl_width, view_left + 2.0f / static_cast(s.frame_max), 1.0f); } window->DrawList->AddRect({tl_init_pos.x - knob_width / 2.0f, tl_init_pos.y}, {tl_init_pos.x + knob_width / 2.0f, tl_init_pos.y + view_height}, @@ -678,45 +690,15 @@ namespace K::UI { window->DrawList->AddRect({tl_init_pos.x + view_width - knob_width / 2.0f, tl_init_pos.y}, {tl_init_pos.x + view_width + knob_width / 2.0f, tl_init_pos.y + view_height}, 0x22FFFFFF); window->DrawList->AddRectFilled({tl_init_pos.x + view_width * view_left - knob_width / 2.0f, tl_init_pos.y}, - {tl_init_pos.x + view_width * view_left + knob_width / 2.0f, tl_init_pos.y + view_height}, - 0xAAFFFFFF, 2.0f); + {tl_init_pos.x + view_width * view_left + knob_width / 2.0f, tl_init_pos.y + view_height}, + 0xAAFFFFFF, 2.0f); window->DrawList->AddRectFilled({tl_init_pos.x + view_width * view_right - knob_width / 2.0f, tl_init_pos.y}, - {tl_init_pos.x + view_width * view_right + knob_width / 2.0f, tl_init_pos.y + view_height}, - 0xAAFFFFFF, 2.0f); + {tl_init_pos.x + view_width * view_right + knob_width / 2.0f, tl_init_pos.y + view_height}, + 0xAAFFFFFF, 2.0f); window->DrawList->AddRectFilled( {tl_init_pos.x + view_width * view_left, tl_init_pos.y + view_height * .2f}, {tl_init_pos.x + view_width * view_right, tl_init_pos.y + view_height * .8f}, 0x33FFFFFF, 2.0f); - // Playhead - window->DrawList->AddLine({tl_init_pos.x + view_width * static_cast(s.current_frame) / - static_cast(s.frame_max + 1), tl_init_pos.y}, - {tl_init_pos.x + view_width * static_cast(s.current_frame) / - static_cast(s.frame_max + 1), - tl_init_pos.y + view_height}, 0xFF3333FF, 2.0f); - - ImGui::SetCursorScreenPos(tl_init_pos - style.CellPadding); - ImGui::InvisibleButton("##TL_BG", ImVec2{ view_width + style.CellPadding.x * 2, row_height + style.CellPadding.y}); - - // Frame Grid - static f32 frame_grid_min_width = 20.0f; - f32 fr_step = 1.0f / static_cast(s.frame_max + 1) * view_width / view_amt, - grid_step = - fr_step >= frame_grid_min_width ? fr_step : std::ceil(frame_grid_min_width / fr_step) * fr_step; - for (f32 fr = TimelineFrameToScreenView(view_left, view_amt, view_width, - std::ceil(view_left * static_cast(s.frame_max + 1)), - s.frame_max) + tl_init_pos.x; - fr < tl_init_pos.x + view_width; fr += grid_step) - window->DrawList->AddLine({fr, tl_init_pos.y + row_height}, - {fr, ImGui::GetWindowSize().y + tl_init_pos.y}, 0x11FFFFFF, 1.0f); - - if (TimelineFrameInView(view_left, view_right, s.current_frame, s.frame_max)) { - f32 fr = TimelineFrameToScreenView(view_left, view_amt, view_width, s.current_frame, s.frame_max) + - tl_init_pos.x; - window->DrawList->AddLine( - {fr, tl_init_pos.y + row_height}, - {fr, ImGui::GetWindowSize().y + tl_init_pos.y}, 0x773333FF, 1.0f); - } - ImGui::PopStyleVar(3); ImGui::SameLine(); ImGui::TableHeader(""); @@ -726,7 +708,7 @@ namespace K::UI { i32 move_from = -1, move_to = -1; bool after{}; - // KF dragging controls + // KF controls static bool started_dragging = false, dragging = false; if (started_dragging) { dragging = true; @@ -828,26 +810,29 @@ namespace K::UI { const static f32 layer_bound_width = 5.0f; i32 l_in = TimelineFrameInView(view_left, view_right, current_layer.in, s.frame_max), l_out = TimelineFrameInView(view_left, view_right, current_layer.out, s.frame_max); - ImGui::SetCursorPosX(ImGui::GetCursorPosX() + TimelineFrameToScreenView(view_left, view_amt, view_width, current_layer.in, s.frame_max)); + f32 in_pos = TimelineFrameToScreenView(view_left, view_amt, view_width, current_layer.in, s.frame_max), + out_pos = TimelineFrameToScreenView(view_left, view_amt, view_width, current_layer.out, s.frame_max); if (l_in) { + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + in_pos); static f32 l_in_old; ImGui::PushStyleColor(ImGuiCol_Button, 0xFF666666); ImGui::Button("##Layer_L", {layer_bound_width, view_height}); if (ImGui::IsItemClicked()) { - l_in_old = TimelineFrameToScreenView(view_left, view_amt, view_width, current_layer.in, s.frame_max); + l_in_old = in_pos; } if (ImGui::IsItemHovered()) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); } if (ImGui::IsItemActive()) { - current_layer.in = std::min(TimelineScreenViewToFrame(view_left, view_amt, view_width, std::clamp(ImGui::GetMouseDragDelta().x + l_in_old, 0.0f, view_width), s.frame_max), current_layer.out); + current_layer.in = std::min(TimelineScreenViewToFrame(view_left, view_amt, view_width, + std::clamp(ImGui::GetMouseDragDelta().x + l_in_old, 0.0f, view_width), s.frame_max), current_layer.out); } ImGui::PopStyleColor(); ImGui::SameLine(0.0f, 0.0f); } - f32 in_pos = TimelineFrameToScreenView(view_left, view_amt, view_width, current_layer.in, s.frame_max); ImGui::PushID(0); - ImGui::Button(current_layer.name.c_str(), {std::min(TimelineFrameToScreenView(view_left, view_amt, view_width, current_layer.out, s.frame_max) + fr_step, view_width) - in_pos - layer_bound_width * static_cast(l_in + l_out), view_height}); + ImGui::Button(current_layer.name.c_str(), + {std::min(out_pos + fr_step, view_width) - static_cast(l_in) * in_pos - layer_bound_width * static_cast(l_in + l_out), view_height}); static u32 in_old, out_old; if (ImGui::IsItemClicked()) { in_old = current_layer.in; @@ -866,7 +851,7 @@ namespace K::UI { ImGui::PushStyleColor(ImGuiCol_Button, 0xFF666666); ImGui::Button("##Layer_R", {layer_bound_width, view_height}); if (ImGui::IsItemClicked()) { - r_out_old = TimelineFrameToScreenView(view_left, view_amt, view_width, current_layer.out, s.frame_max); + r_out_old = out_pos; } if (ImGui::IsItemHovered()) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); @@ -1016,6 +1001,10 @@ namespace K::UI { ImGui::TableSetColumnIndex(2); auto& [chain, chain_copy] = *std::any_cast(&info.p->extra); + + if (tl_clear_selection_request) + chain.selected.clear(); + bool m_empty = chain.segments.empty(), m_has_before = !m_empty && chain.segments.begin()->frame >= s.current_frame; ImGui::BeginDisabled(m_empty || m_has_before); @@ -1032,7 +1021,7 @@ namespace K::UI { f32 v = std::get::type>( PlugboardGraph::Eval(s, info)); - if (ImGui::DragFloat("##value", &v)) { + if (ImGui::DragFloat("##value", &v, 0.005f)) { auto l = std::lower_bound(chain.segments.begin(), chain.segments.end(), s.current_frame, [](const PlugboardNodes::ChainSegment& a, i32 b) { return a.frame < b; }); if (l != chain.segments.end() && l->frame == static_cast(s.current_frame)) @@ -1070,9 +1059,6 @@ namespace K::UI { chain.selected.clear(); key_loop_target = &chain_copy; } - if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !io.KeyCtrl) { - chain.selected.clear(); - } for (u32 ii = 0; ii < key_loop_target->segments.size(); ii++) { const auto& [k, val, segment] = (key_loop_target->segments)[ii]; ImGui::PushID(k); @@ -1121,6 +1107,8 @@ namespace K::UI { started_dragging = true; // setup dragging & start on next frame if (!is_sel || ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + if (!io.KeyCtrl) + chain.selected.clear(); chain.selected.push_back(ii); } } @@ -1164,6 +1152,8 @@ namespace K::UI { if (started_dragging && dragging) started_dragging = false; + if (tl_clear_selection_request_this_frame && tl_clear_selection_request) + tl_clear_selection_request = false; tl_end_begin = {tl_init_pos.x, ImGui::GetCursorScreenPos().y}; @@ -1209,12 +1199,107 @@ namespace K::UI { ImGui::EndChild(); ImGui::SetCursorScreenPos(tl_end_begin); - if (ImGui::BeginChild("TL Overlay")) { + if (ImGui::BeginChild("TL Bottom Unfilled Drag Overlay")) { draw_tl_bg_drag_area(ImGui::GetContentRegionAvail().y); tl_scroll_zoom(); } ImGui::EndChild(); + ImVec2 tl_grid_cursor_pos = {tl_init_pos.x, tl_init_pos.y + row_height}; + if (show_curve_editor) { + ImGui::SetCursorScreenPos(tl_grid_cursor_pos); + ImGui::PushStyleColor(ImGuiCol_ChildBg, 0xFF111111); + if (ImGui::BeginChild("Curve Editor", ImGui::GetContentRegionAvail())) { + draw_tl_bg_drag_area(ImGui::GetContentRegionAvail().y); + tl_scroll_zoom(); + ImGui::SetCursorScreenPos(tl_grid_cursor_pos); + + ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, {0.0f, 0.0f}); + ImPlot::PushStyleVar(ImPlotStyleVar_FitPadding, {.1f, .1f}); + ImPlot::PushStyleColor(ImPlotCol_FrameBg, 0); + ImPlot::PushStyleColor(ImPlotCol_PlotBg, 0); + ImPlot::PushStyleColor(ImPlotCol_PlotBorder, 0); + if (ImPlot::BeginPlot("uniform", {view_width, ImGui::GetContentRegionAvail().y}, ImPlotFlags_CanvasOnly | ImPlotFlags_NoFrame | ImPlotFlags_NoInputs)) { + ImPlot::SetupAxis(ImAxis_X1, "time", ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_NoHighlight); + ImPlot::SetupAxis(ImAxis_Y1, "val", ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_AutoFit); +// struct PlotInfo { f32 begin, end; i32 samples; const CompState &s; PlugboardGraph::ConnectInfo &connected_v; }; +// struct PlotInfo info { +// .begin = view_left * static_cast(s.frame_max + 1), +// .end = view_right * static_cast(s.frame_max + 1), +// .samples = 200, +// .s = s, +// .connected_v = std::get<1>(connected_v) +// }; +// ImPlot::SetupAxisLimits(ImAxis_X1, info.begin, info.end, ImGuiCond_Always); +// ImPlot::SetupFinish(); +// ImPlot::PlotLineG("v", [](int idx, void* user_data) -> ImPlotPoint { +// PlotInfo i = *(PlotInfo*)user_data; +// f32 x = i.begin + (i.end - i.begin) * static_cast(idx) / static_cast(i.samples); +// CompState ss = i.s; +// ss.current_frame = static_cast(std::round(x)); +// bool good; +// Vector info; +// return {x, std::get::type>(PlugboardGraph::ConvertValue(PlugboardGraph::Eval(ss, i.connected_v, &info), PlugboardGraph::T_Float, good))}; +// }, &info, info.samples); + static float xs1[100], ys1[100]; + for (int i = 0; i < 100; ++i) { + xs1[i] = i * 0.01f; + ys1[i] = xs1[i] + 0.1f * ((float)rand() / (float)RAND_MAX); + } + static float xs2[50], ys2[50]; + for (int i = 0; i < 50; i++) { + xs2[i] = 0.25f + 0.2f * ((float)rand() / (float)RAND_MAX); + ys2[i] = 0.75f + 0.2f * ((float)rand() / (float)RAND_MAX); + } + + ImPlot::PlotScatter("Data 1", xs1, ys1, 100); + ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f); + ImPlot::SetNextMarkerStyle(ImPlotMarker_Square, 6, ImPlot::GetColormapColor(1), IMPLOT_AUTO, ImPlot::GetColormapColor(1)); + ImPlot::PlotScatter("Data 2", xs2, ys2, 50); + ImPlot::PopStyleVar(); + + ImPlot::EndPlot(); + } + ImPlot::PopStyleColor(3); + ImPlot::PopStyleVar(2); + } + ImGui::EndChild(); + ImGui::PopStyleColor(); + } + + ImGui::SetCursorScreenPos(tl_grid_cursor_pos); + if (ImGui::BeginChild("TL Grid Overlay", ImGui::GetContentRegionAvail(), ImGuiChildFlags_None, ImGuiWindowFlags_NoInputs)) { + // Playhead + auto window = ImGui::GetCurrentWindow(); + window->DrawList->AddLine({tl_init_pos.x + view_width * static_cast(s.current_frame) / + static_cast(s.frame_max + 1), tl_init_pos.y}, + {tl_init_pos.x + view_width * static_cast(s.current_frame) / + static_cast(s.frame_max + 1), + tl_init_pos.y + view_height}, 0xFF3333FF, 2.0f); + + ImGui::SetCursorScreenPos(tl_init_pos - style.CellPadding); + ImGui::InvisibleButton("##TL_BG", ImVec2{ view_width + style.CellPadding.x * 2, row_height + style.CellPadding.y}); + + // Frame Grid + static f32 frame_grid_min_width = 20.0f; + f32 grid_step = + fr_step >= frame_grid_min_width ? fr_step : std::ceil(frame_grid_min_width / fr_step) * fr_step; + for (f32 fr = TimelineFrameToScreenView(view_left, view_amt, view_width, + std::ceil(view_left * static_cast(s.frame_max + 1)), + s.frame_max) + tl_init_pos.x; + fr < tl_init_pos.x + view_width; fr += grid_step) + window->DrawList->AddLine({fr, tl_init_pos.y + row_height}, + {fr, ImGui::GetWindowSize().y + tl_init_pos.y}, 0x11FFFFFF, 1.0f); + + if (TimelineFrameInView(view_left, view_right, s.current_frame, s.frame_max)) { + f32 fr = TimelineFrameToScreenView(view_left, view_amt, view_width, s.current_frame, s.frame_max) + + tl_init_pos.x; + window->DrawList->AddLine( + {fr, tl_init_pos.y + row_height}, + {fr, ImGui::GetWindowSize().y + tl_init_pos.y}, 0x773333FF, 1.0f); + } + } + ImGui::EndChild(); ImGui::End(); } @@ -1337,70 +1422,11 @@ namespace K::UI { if (ImGui::Begin("Interpolation", &draw_interpolation, ImGuiWindowFlags_NoScrollbar)) { ImGuiIO& io = ImGui::GetIO(); + static PlugboardNodes::K_Interpolation type_current = PlugboardNodes::K_I_CubicBezier; + static const ImVec2 p1 { 0.0f, 0.0f}, p4 {1.0f, 1.0f}; static ImVec2 p2 { 1.0f, 0.0f }, p3 { 0.0f, 1.0f }, p2_old = p2, p3_old = p3; - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {10.0f, 10.0f}); - f32 y_max = std::max(std::max(p2.y, p3.y), p4.y), - y_min = std::min(std::min(p1.y, p2.y), p3.y), - y_range = y_max - y_min; - - f32 w_window = ImGui::GetContentRegionAvail().x; - if (ImGui::BeginChild("Canvas", {w_window, w_window}, ImGuiChildFlags_Border, ImGuiWindowFlags_NoScrollbar)) { - f32 w = ImGui::GetContentRegionAvail().x; - ImVec2 pos = ImGui::GetCursorScreenPos(); - auto *dl = ImGui::GetWindowDrawList(); - ImVec2 tp1 = pos + ImVec2{p1.x * w, ((1.0f - p1.y) - (1.0f - y_max)) * w / y_range}, - tp4 = pos + ImVec2{p4.x * w, ((1.0f - p4.y) - (1.0f - y_max)) * w / y_range}, - tp2 = pos + ImVec2{p2.x * w, ((1.0f - p2.y) - (1.0f - y_max)) * w / y_range}, - tp3 = pos + ImVec2{p3.x * w, ((1.0f - p3.y) - (1.0f - y_max)) * w / y_range}; - dl->AddBezierCubic(tp1, tp2, tp3, tp4, 0xFFFFFFFF, 2.0f); - dl->AddLine(tp1, tp2, 0xAAFFFFFF, 1.5f); - dl->AddLine(tp3, tp4, 0xAAFFFFFF, 1.5f); - dl->AddLine(tp1, {tp1.x + w, tp1.y}, 0x44FFFFFF, 2.0f); - dl->AddLine(tp4, {tp4.x - w, tp4.y}, 0x44FFFFFF, 2.0f); - dl->AddText(tp1 + ImVec2{10.0f, -20.0f}, 0xFFFFFFFF, "0.0"); - dl->AddText(tp4 + ImVec2{-30.0f, 10.0f}, 0xFFFFFFFF, "1.0"); - - f32 x = std::clamp((io.MousePos.x - pos.x) / w, 0.0f, 1.0f); - f32 y = Graphics::InjectingCubicBezierFromX(p2.x, p2.y, p3.x, p3.y, x); - dl->AddLine(pos + ImVec2{0, ((1.0f - y) - (1.0f - y_max)) * w / y_range}, pos + ImVec2{w, ((1.0f - y) - (1.0f - y_max)) * w / y_range}, 0xBB5555FF, 1.0f); - dl->AddLine(pos + ImVec2{x * w, 0.0f}, pos + ImVec2{x * w, w}, 0x44FF5555, 1.0f); - - ImGui::SetCursorScreenPos({tp1.x, ((1.0f - y) - (1.0f - y_max)) * w / y_range + pos.y}); - ImGui::Text("%.2f, %.2f", x, y); - - ImGui::SetCursorPos(ImVec2{0.0f, 20.0f}); - ImGui::Text("%.2f", y_max); - - ImGui::SetCursorPos(ImVec2{w * .8f, w - 15.0f,}); - ImGui::Text("%.2f", y_min); - - ImVec2 size = {15.0f, 15.0f}; - ImGui::SetCursorScreenPos(tp2 - size / 2.0f); - ImVec2 off = io.MousePos - io.MouseClickedPos[0]; - ImGui::Button("##P2", size); - if (ImGui::IsItemClicked()) { - p2_old = p2; - } - if (ImGui::IsItemActive()) { - p2 = p2_old + ImVec2{off.x / w, off.y / -w * y_range}; - p2.x = std::clamp(p2.x, 0.0f, 1.0f); - } - - ImGui::SetCursorScreenPos(tp3 - size / 2.0f); - ImGui::Button("##P3", size); - if (ImGui::IsItemClicked()) { - p3_old = p3; - } - if (ImGui::IsItemActive()) { - p3 = p3_old + ImVec2{off.x / w, off.y / -w * y_range}; - p3.x = std::clamp(p3.x, 0.0f, 1.0f); - } - } - ImGui::PopStyleVar(1); - ImGui::EndChild(); - if (ImGui::Button("Apply")) for (auto& layer : s.layers) for (auto& u : layer.track.uniforms) @@ -1409,11 +1435,97 @@ namespace K::UI { for (auto it = chain.segments.begin(); it != chain.segments.end(); it++) { auto nit = std::next(it); if (nit != chain.segments.end() && std::find(chain.selected.begin(), chain.selected.end(), std::distance(chain.segments.begin(), it)) != chain.selected.end() && - std::find(chain.selected.begin(), chain.selected.end(), std::distance(chain.segments.begin(), nit)) != chain.selected.end()) { - it->interp = {PlugboardNodes::K_I_CubicBezier, {p2.x, p2.y, p3.x, p3.y}}; + std::find(chain.selected.begin(), chain.selected.end(), std::distance(chain.segments.begin(), nit)) != chain.selected.end()) { + it->interp = {type_current, {p2.x, p2.y, p3.x, p3.y}}; } } } + + ImGui::SameLine(); + + ImGui::Combo("##Type", (i32*)&type_current, PlugboardNodes::K_Interpolation_Names, PlugboardNodes::K_I_Count); + + f32 w_window = std::min(ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y); + if (type_current == PlugboardNodes::K_I_CubicBezier) { + f32 y_max = std::max(std::max(p2.y, p3.y), p4.y), + y_min = std::min(std::min(p1.y, p2.y), p3.y), + y_range = y_max - y_min; + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {10.0f, 10.0f}); + if (ImGui::BeginChild("Canvas", {w_window, w_window}, ImGuiChildFlags_Border, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { + f32 w = ImGui::GetContentRegionAvail().x; + ImVec2 pos = ImGui::GetCursorScreenPos(); + auto *dl = ImGui::GetWindowDrawList(); + ImVec2 tp1 = pos + ImVec2{p1.x * w, ((1.0f - p1.y) - (1.0f - y_max)) * w / y_range}, + tp4 = pos + ImVec2{p4.x * w, ((1.0f - p4.y) - (1.0f - y_max)) * w / y_range}, + tp2 = pos + ImVec2{p2.x * w, ((1.0f - p2.y) - (1.0f - y_max)) * w / y_range}, + tp3 = pos + ImVec2{p3.x * w, ((1.0f - p3.y) - (1.0f - y_max)) * w / y_range}; + dl->AddBezierCubic(tp1, tp2, tp3, tp4, 0xFFFFFFFF, 2.0f); + dl->AddLine(tp1, tp2, 0xAAFFFFFF, 1.5f); + dl->AddLine(tp3, tp4, 0xAAFFFFFF, 1.5f); + dl->AddLine(tp1, {tp1.x + w, tp1.y}, 0x44FFFFFF, 2.0f); + dl->AddLine(tp4, {tp4.x - w, tp4.y}, 0x44FFFFFF, 2.0f); + dl->AddText(tp1 + ImVec2{10.0f, -20.0f}, 0xFFFFFFFF, "0.0"); + dl->AddText(tp4 + ImVec2{-30.0f, 10.0f}, 0xFFFFFFFF, "1.0"); + + f32 x = std::clamp((io.MousePos.x - pos.x) / w, 0.0f, 1.0f); + f32 y = Graphics::InjectingCubicBezierFromX(p2.x, p2.y, p3.x, p3.y, x); + dl->AddLine(pos + ImVec2{0, ((1.0f - y) - (1.0f - y_max)) * w / y_range}, pos + ImVec2{w, ((1.0f - y) - (1.0f - y_max)) * w / y_range}, 0xBB5555FF, 1.0f); + dl->AddLine(pos + ImVec2{x * w, 0.0f}, pos + ImVec2{x * w, w}, 0x44FF5555, 1.0f); + + ImGui::SetCursorScreenPos({tp1.x, ((1.0f - y) - (1.0f - y_max)) * w / y_range + pos.y}); + ImGui::Text("%.2f, %.2f", x, y); + + ImGui::SetCursorPos(ImVec2{0.0f, 20.0f}); + ImGui::Text("%.2f", y_max); + + ImGui::SetCursorPos(ImVec2{w * .8f, w - 15.0f,}); + ImGui::Text("%.2f", y_min); + + ImVec2 size = {15.0f, 15.0f}; + ImGui::SetCursorScreenPos(tp2 - size / 2.0f); + ImVec2 off = io.MousePos - io.MouseClickedPos[0]; + ImGui::Button("##P2", size); + if (ImGui::IsItemClicked()) { + p2_old = p2; + } + if (ImGui::IsItemActive()) { + p2 = p2_old + ImVec2{off.x / w, off.y / -w * y_range}; + p2.x = std::clamp(p2.x, 0.0f, 1.0f); + } + + ImGui::SetCursorScreenPos(tp3 - size / 2.0f); + ImGui::Button("##P3", size); + if (ImGui::IsItemClicked()) { + p3_old = p3; + } + if (ImGui::IsItemActive()) { + p3 = p3_old + ImVec2{off.x / w, off.y / -w * y_range}; + p3.x = std::clamp(p3.x, 0.0f, 1.0f); + } + } + ImGui::PopStyleVar(1); + ImGui::EndChild(); + } + else { + ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, {0.0f, 0.0f}); + ImPlot::PushStyleVar(ImPlotStyleVar_FitPadding, {.1f, .1f}); + ImPlot::PushStyleColor(ImPlotCol_FrameBg, 0); + ImPlot::PushStyleColor(ImPlotCol_PlotBg, 0); + ImPlot::PushStyleColor(ImPlotCol_PlotBorder, 0); + if (ImPlot::BeginPlot("uniform", {w_window, w_window}, ImPlotFlags_CanvasOnly | ImPlotFlags_NoInputs)) { + ImPlot::SetupAxis(ImAxis_X1, "time", ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_AutoFit); + ImPlot::SetupAxis(ImAxis_Y1, "val", ImPlotAxisFlags_Opposite | ImPlotAxisFlags_NoLabel | ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_AutoFit); + ImPlot::SetupFinish(); + constexpr static u32 samples = 100; + ImPlot::PlotLineG("v", [](i32 idx, void* user_data) { + f32 t = static_cast(idx) / static_cast(samples); + return ImPlotPoint{t, PlugboardNodes::EvalInterpolation(t, {.interp = type_current})}; + }, nullptr, samples); + ImPlot::EndPlot(); + } + ImPlot::PopStyleColor(3); + ImPlot::PopStyleVar(2); + } } ImGui::End(); } diff --git a/Keishiki/include/PlugboardNodes.h b/Keishiki/include/PlugboardNodes.h index 0a1fcb1..068e47e 100644 --- a/Keishiki/include/PlugboardNodes.h +++ b/Keishiki/include/PlugboardNodes.h @@ -102,6 +102,109 @@ namespace K::PlugboardNodes { f32 v[4]; }; + constexpr f32 EvalInterpolation(f32 x, const InterpolationExtra& extra) { + switch (extra.interp) { + case K_I_InConstant: + return 0.0f; + case K_I_OutConstant: + return 1.0f; + case K_I_Linear: + return x; + case K_I_Step: + return bx::getEaseFunc(bx::Easing::Step)(x); + case K_I_SmoothStep: + return bx::getEaseFunc(bx::Easing::SmoothStep)(x); + case K_I_InQuadratic: + return bx::getEaseFunc(bx::Easing::InQuad)(x); + case K_I_OutQuadratic: + return bx::getEaseFunc(bx::Easing::OutQuad)(x); + case K_I_InOutQuadratic: + return bx::getEaseFunc(bx::Easing::InOutQuad)(x); + case K_I_OutInQuadratic: + return bx::getEaseFunc(bx::Easing::OutInQuad)(x); + case K_I_InCubic: + return bx::getEaseFunc(bx::Easing::InCubic)(x); + case K_I_OutCubic: + return bx::getEaseFunc(bx::Easing::OutCubic)(x); + case K_I_InOutCubic: + return bx::getEaseFunc(bx::Easing::InOutCubic)(x); + case K_I_OutInCubic: + return bx::getEaseFunc(bx::Easing::OutInCubic)(x); + case K_I_InQuartic: + return bx::getEaseFunc(bx::Easing::InQuart)(x); + case K_I_OutQuartic: + return bx::getEaseFunc(bx::Easing::OutQuart)(x); + case K_I_InOutQuartic: + return bx::getEaseFunc(bx::Easing::InOutQuart)(x); + case K_I_OutInQuartic: + return bx::getEaseFunc(bx::Easing::OutInQuart)(x); + case K_I_InQuintic: + return bx::getEaseFunc(bx::Easing::InQuint)(x); + case K_I_OutQuintic: + return bx::getEaseFunc(bx::Easing::OutInQuint)(x); + case K_I_InOutQuintic: + return bx::getEaseFunc(bx::Easing::InOutQuint)(x); + case K_I_OutInQuintic: + return bx::getEaseFunc(bx::Easing::OutInQuint)(x); + case K_I_InSine: + return bx::getEaseFunc(bx::Easing::InSine)(x); + case K_I_OutSine: + return bx::getEaseFunc(bx::Easing::OutSine)(x); + case K_I_InOutSine: + return bx::getEaseFunc(bx::Easing::InOutSine)(x); + case K_I_OutInSine: + return bx::getEaseFunc(bx::Easing::OutInSine)(x); + case K_I_InExponential: + return bx::getEaseFunc(bx::Easing::InExpo)(x); + case K_I_OutExponential: + return bx::getEaseFunc(bx::Easing::OutExpo)(x); + case K_I_InOutExponential: + return bx::getEaseFunc(bx::Easing::InOutExpo)(x); + case K_I_OutInExponential: + return bx::getEaseFunc(bx::Easing::OutInExpo)(x); + case K_I_InCircular: + return bx::getEaseFunc(bx::Easing::InCirc)(x); + case K_I_OutCircular: + return bx::getEaseFunc(bx::Easing::OutCirc)(x); + case K_I_InOutCircular: + return bx::getEaseFunc(bx::Easing::InOutCirc)(x); + case K_I_OutInCircular: + return bx::getEaseFunc(bx::Easing::OutInCirc)(x); + case K_I_InElastic: + return bx::getEaseFunc(bx::Easing::InElastic)(x); + case K_I_OutElastic: + return bx::getEaseFunc(bx::Easing::OutElastic)(x); + case K_I_InOutElastic: + return bx::getEaseFunc(bx::Easing::InOutElastic)(x); + case K_I_OutInElastic: + return bx::getEaseFunc(bx::Easing::OutInElastic)(x); + case K_I_InBack: + return bx::getEaseFunc(bx::Easing::InBack)(x); + case K_I_OutBack: + return bx::getEaseFunc(bx::Easing::OutBack)(x); + case K_I_InOutBack: + return bx::getEaseFunc(bx::Easing::InOutBack)(x); + case K_I_OutInBack: + return bx::getEaseFunc(bx::Easing::OutInBack)(x); + case K_I_InBounce: + return bx::getEaseFunc(bx::Easing::InBounce)(x); + case K_I_OutBounce: + return bx::getEaseFunc(bx::Easing::OutBounce)(x); + case K_I_InOutBounce: + return bx::getEaseFunc(bx::Easing::InOutBounce)(x); + case K_I_OutInBounce: + return bx::getEaseFunc(bx::Easing::OutInBounce)(x); + case K_I_CubicBezier: { + f32 p2x = extra.v[0], p2y = extra.v[1], p3x = extra.v[2], p3y = extra.v[3]; + f32 a = 0.0f, b = p2x, c = p3x, d = 1.0f; + f64 t = Graphics::GetCubicUniqueReal(-a+3.0f*b-3.0f*c+d, 3.0f*a-6.0f*b+3.0f*c, -3.0f*a+3.0f*b,a-x); + return static_cast(Graphics::CubicBezier(0.0f, p2y, p3y, 1.0f, t)); + } + default: + return {}; + } + } + extern PlugboardGraph::Node Interpolation; struct ChainSegment { diff --git a/TODO.md b/TODO.md index a507f2f..e9c5424 100644 --- a/TODO.md +++ b/TODO.md @@ -1,11 +1,10 @@ # NOW ## UI -- Undo's - - Once this is done we can move the plugboard loop detection/chain finding out of the eval loop - Keys - - selection in comp panel - deletion - copy & paste +- Undo's + - Once this is done we can move the plugboard loop detection/chain finding out of the eval loop - Graph editor - Node groups - Simple export @@ -37,6 +36,7 @@ ## Compositor - Simple 3D engine - Flat sets and flat maps pending compiler support +- Random Idea: Array-eval nodes ## UI - Adapt nodes for shader graph -- code editor will be fine for now