diff --git a/Keishiki/CMakeLists.txt b/Keishiki/CMakeLists.txt index 5f98a90..2596e76 100644 --- a/Keishiki/CMakeLists.txt +++ b/Keishiki/CMakeLists.txt @@ -22,6 +22,13 @@ add_subdirectory("ext/bgfx") add_compile_definitions(IMGUI_DEFINE_MATH_OPERATORS) +# Disable RTTI +if(MSVC) + string(REGEX REPLACE "/GR" "/GR-" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") +else() + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") +endif() + if (WIN32) include_directories("include/windows") diff --git a/Keishiki/Keishiki.cpp b/Keishiki/Keishiki.cpp index 6897e97..6563fb9 100644 --- a/Keishiki/Keishiki.cpp +++ b/Keishiki/Keishiki.cpp @@ -26,6 +26,16 @@ namespace { } namespace K { + PlugboardGraph::T_Map::type FetchCompFrame(const CompState& s, const PlugboardGraph::NodeInstance& n) { + return PlugboardGraph::T_Map::type{static_cast(s.current_frame)}; + } + PlugboardGraph::T_Map::type FetchCompTime(const CompState& s, const PlugboardGraph::NodeInstance& n) { + return PlugboardGraph::T_Map::type{static_cast(s.current_frame) / s.fps}; + } + PlugboardGraph::T_Map::type FetchAppTicks(const CompState& s, const PlugboardGraph::NodeInstance& n) { + return PlugboardGraph::T_Map::type{ static_cast(SDL_GetTicks64()) }; + } + bool Init() { if (SDL_Init(SDL_INIT_VIDEO)) { LogError(SDL_GetError()); @@ -101,9 +111,7 @@ namespace K { {}, {{"Frame", PlugboardGraph::T_Int}, {"Time (s)", PlugboardGraph::T_Float}, {"App Ticks", PlugboardGraph::T_Float}}, { - [](const CompState& s, const Vector::type>& v) { return PlugboardGraph::T_Map::type{static_cast(s.current_frame)}; }, - [](const CompState& s, const Vector::type>& v) { return PlugboardGraph::T_Map::type{static_cast(s.current_frame) / s.fps}; }, - [](const CompState& s, const Vector::type>& v) { return PlugboardGraph::T_Map::type{ static_cast(SDL_GetTicks()) / 18.0f }; } // kill me + FetchCompFrame, FetchCompTime, FetchAppTicks } }; state.plugboard.comp_out = PlugboardGraph::Node{ diff --git a/Keishiki/PlugboardGraph.cpp b/Keishiki/PlugboardGraph.cpp index 96b96b7..2aa226a 100644 --- a/Keishiki/PlugboardGraph.cpp +++ b/Keishiki/PlugboardGraph.cpp @@ -25,22 +25,7 @@ namespace K::PlugboardGraph { } T_Map::type Eval(const CompState& s, const ConnectInfo& info) { - Vector::type> pack; - for (u32 i = 0; i < info.p->inputs_fed.size(); i++) { - bool good; - auto val = ConvertValue(std::visit([&s](auto&& arg) { - using T = std::decay_t; - if constexpr (std::is_same_v) - return Eval(s,arg); - else if constexpr (std::is_same_v::type>) - return arg; - }, info.p->inputs_fed[i]), info.p->node->in[i].type, good); - if (good) { - pack.push_back(val); - } - else return {}; // todo panic! - } - return info.p->node->fetch[info.index](s, pack); + return info.p->node->fetch[info.index](s, *info.p); } NodeInstance MakeInstance(Node& n) { @@ -50,11 +35,14 @@ namespace K::PlugboardGraph { inp.emplace_back(expand_type(t.type)); for (auto t : n.out) out.emplace_back(); - return { + auto instance = NodeInstance{ .node = &n, .inputs_fed = std::move(inp), .outputs_going = std::move(out) }; + if (n.extra_setup != nullptr) + n.extra_setup(instance); + return instance; } T_Map::type ConvertValue(const T_Map::type& v, Type target, bool& good) { @@ -66,7 +54,7 @@ namespace K::PlugboardGraph { case T_Float: return arg; case T_Int: - return static_cast::type>(arg); + return static_cast::type>(std::round(arg)); case T_RGBA: return RGBA{ arg, arg, arg, arg }; case T_XY: diff --git a/Keishiki/UI.cpp b/Keishiki/UI.cpp index 312495c..57dc53a 100644 --- a/Keishiki/UI.cpp +++ b/Keishiki/UI.cpp @@ -19,7 +19,7 @@ namespace { draw_shader = true, draw_comp = true, draw_interpolation = true, - draw_properties = true, +// draw_properties = true, draw_assets = true; const f32 row_height = 20.0f; @@ -117,6 +117,22 @@ namespace K::UI { layer.track.clear(); } + void DrawPlugboardVariableEditWidget(PlugboardGraph::T_Map::type& var) { + std::visit([](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v::type>) + ImGui::DragFloat("##", &arg, 0.005f); + else if constexpr (std::is_same_v::type>) + ImGui::DragInt("##", &arg, 0.005f); + else if constexpr (std::is_same_v::type>) + ImGui::DragFloat4("##", &arg.r, 0.005f); + else if constexpr (std::is_same_v::type>) + ImGui::DragFloat2("##", &arg.x, 0.005f); + else if constexpr (std::is_same_v::type>) + ImGui::DragFloat3("##", &arg.x, 0.005f); + }, var); + } + void PlugboardNodeDrawSockets(PlugboardGraph::NodeInstance& n, ImGuiStyle& style, bool& dragging_on_socket, ImVec2& source, std::unordered_map& link_pos, bool dummy = true) { @@ -177,34 +193,42 @@ namespace K::UI { ImGui::EndDragDropTarget(); } + auto socket_link_pos = ImGui::GetCursorScreenPos() + ImVec2{ ImGui::GetFrameHeight() / 2, -ImGui::GetFrameHeight() / 2 - style.ItemInnerSpacing.y}; + + ImGui::TableNextColumn(); + ImGui::TextUnformatted(n.node->in[in].name.c_str()); + // Update link info - std::visit([&dragging_on_socket, &source, &link_pos, &style](auto&& arg) { + std::visit([&socket_link_pos, &dragging_on_socket, &source, &link_pos, &style](auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v) { if (ImGui::IsItemActive()) link_pos[arg].sinks.push_back(ImGui::GetMousePos()); else - link_pos[arg].sinks.push_back(ImGui::GetCursorScreenPos() + ImVec2{ ImGui::GetFrameHeight() / 2, -ImGui::GetFrameHeight() / 2 - style.ItemInnerSpacing.y}); + link_pos[arg].sinks.push_back(socket_link_pos); } else if (ImGui::IsItemActive()) { dragging_on_socket = true; - source = ImGui::GetCursorScreenPos() + ImVec2{ ImGui::GetFrameHeight() / 2, -ImGui::GetFrameHeight() / 2 - style.ItemInnerSpacing.y}; + source = socket_link_pos; + } + if constexpr (std::is_same_v::type>) { + ImGui::SameLine(); + DrawPlugboardVariableEditWidget(arg); } }, n.inputs_fed[in]); - ImGui::TableNextColumn(); - ImGui::TextUnformatted(n.node->in[in].name.c_str()); ImGui::TableNextColumn(); ImGui::PopID(); } } - void PlugboardDrawNode(PlugboardGraph::NodeInstance& n, ImVec2& view_pos, i32 id, ImGuiIO& io, ImGuiStyle& style, - bool& dragging_on_socket, ImVec2& source, std::unordered_map& link_pos) { + bool PlugboardDrawNode(PlugboardGraph::NodeInstance& n, ImVec2& view_pos, i32 id, ImGuiIO& io, ImGuiStyle& style, + bool& dragging_on_socket, ImVec2& source, std::unordered_map& link_pos, bool selected) { + bool stat = false; ImGui::SetCursorPos(view_pos + n.pos); ImGui::PushID(id); - ImGui::PushStyleColor(ImGuiCol_ChildBg, 0xAA080813); + ImGui::PushStyleColor(ImGuiCol_ChildBg, selected ? 0xAA202040 : 0xAA080813); if (ImGui::BeginChild(n.node->name.c_str(), {}, ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_Border)) { ImGui::PopStyleColor(); if (ImGui::BeginTable(n.node->name.c_str(), 3)) { @@ -214,12 +238,21 @@ namespace K::UI { ImGui::Button(n.node->name.c_str(), {100.0f , 0.0f}); if (ImGui::IsItemClicked()) { n.old_pos = n.pos; + stat = true; } if (ImGui::IsItemActive()) { n.pos = n.old_pos + (io.MousePos - io.MouseClickedPos[0]); } ImGui::TableNextColumn(); + if (n.node->special_draw != nullptr) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + n.node->special_draw(n); + ImGui::TableNextColumn(); + } + PlugboardNodeDrawSockets(n, style, dragging_on_socket, source, link_pos); ImGui::EndTable(); @@ -227,6 +260,7 @@ namespace K::UI { } ImGui::EndChild(); ImGui::PopID(); + return stat; } void Viewport(CompState& s) { @@ -280,8 +314,25 @@ namespace K::UI { return; } - if (ImGui::Button("Add Sine")) - s.plugboard.nodes.push_back(PlugboardGraph::MakeInstance(PlugboardNodes::Sine)); + static u32 node_current{}; + if (ImGui::Button("Add")) { + auto n = PlugboardGraph::MakeInstance(*PlugboardNodes::Nodes[node_current]); + s.plugboard.nodes.push_back(std::move(n)); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(150.0f); + if (ImGui::BeginCombo("##Node", PlugboardNodes::Nodes[node_current]->name.c_str(), ImGuiComboFlags_None)) { + for (int n = 0; n < PlugboardNodes::Nodes.size(); n++) { + const bool is_selected = (node_current == n); + if (ImGui::Selectable(PlugboardNodes::Nodes[n]->name.c_str(), is_selected)) + node_current = n; + + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } ImGui::SameLine(); ImGui::Text("%lu / %lu Frames", s.current_frame, s.frame_max); @@ -294,7 +345,9 @@ namespace K::UI { auto l = Layer{ VisualTrack{}, name, - Graphics::K_V_AlphaOver + Graphics::K_V_AlphaOver, + 0UL, + s.frame_max }; l.track.add_uniform("aspect_ratio", ShaderGraph::expand_type(ShaderGraph::T_XY)); if (s.width > s.height) @@ -373,9 +426,12 @@ namespace K::UI { ImVec2 drag_source{}; ImGui::PushStyleColor(ImGuiCol_ChildBg, 0xFF100500); + ImGui::SetNextWindowSizeConstraints({avail.x * .1f, -1}, { avail.x * .8f, -1 }); if (ImGui::BeginChild("Nodes", {avail.x * .4f, avail.y}, ImGuiChildFlags_ResizeX, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar)) { ImGui::PopStyleColor(); + static u32 selected; + ImGuiWindow *window = ImGui::GetCurrentWindow(); ImVec2 window_pos = ImGui::GetWindowPos(); nodes_begin = window_pos; @@ -407,7 +463,8 @@ namespace K::UI { u32 i = 0; for (auto it = s.plugboard.nodes.begin(); it != s.plugboard.nodes.end(); it++, i++) - PlugboardDrawNode(*it, view_pos, static_cast(i), io, style, dragging_on_socket, drag_source, s.plugboard.links_pos); + if (PlugboardDrawNode(*it, view_pos, static_cast(i), io, style, dragging_on_socket, drag_source, s.plugboard.links_pos, selected == i)) + selected = i; ImGui::SetCursorPos({}); ImGui::PushStyleColor(ImGuiCol_ChildBg, 0x33FFFFFF); @@ -462,9 +519,9 @@ namespace K::UI { ImGui::TableSetColumnIndex(4); auto *window = ImGui::GetCurrentWindow(); - const f32 knob_width = 8.0f; + const static f32 knob_width = 8.0f; tl_init_pos = ImGui::GetCursorScreenPos(); - view_width = ImGui::GetColumnWidth(), + view_width = ImGui::GetColumnWidth() - 10.0f, tl_width = view_width - knob_width, view_height = ImGui::TableGetHeaderRowHeight(), control_left = tl_init_pos.x, @@ -511,15 +568,15 @@ namespace K::UI { view_right = std::clamp(view_right_old + delta_x, view_left, 1.0f); } - window->DrawList->AddLine({tl_init_pos.x, tl_init_pos.y}, {tl_init_pos.x, tl_init_pos.y + view_height}, - 0x22FFFFFF, 2.0f); - window->DrawList->AddLine({tl_init_pos.x + view_width, tl_init_pos.y}, - {tl_init_pos.x + view_width, tl_init_pos.y + view_height}, 0x22FFFFFF, 2.0f); - window->DrawList->AddLine({tl_init_pos.x + view_width * view_left, tl_init_pos.y}, - {tl_init_pos.x + view_width * view_left, tl_init_pos.y + view_height}, + 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}, + 0x22FFFFFF); + 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); - window->DrawList->AddLine({tl_init_pos.x + view_width * view_right, tl_init_pos.y}, - {tl_init_pos.x + view_width * view_right, tl_init_pos.y + view_height}, + 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); window->DrawList->AddRectFilled( {tl_init_pos.x + view_width * view_left, tl_init_pos.y + view_height * .2f}, @@ -560,10 +617,10 @@ namespace K::UI { ImGui::TableHeader(""); // Main rows -// ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, {0.0f, 0.0f}); i32 move_from = -1, move_to = -1; bool after{}; for (u32 i = 0; i < s.layers.size(); i++) { + auto& current_layer = s.layers[i]; const bool selected = s.selected.contains(i), disabled = s.disabled.contains(i); ImGui::PushID(static_cast(i)); ImGui::TableNextRow(ImGuiTableRowFlags_None, row_height); @@ -579,8 +636,7 @@ namespace K::UI { flags |= ImGuiTreeNodeFlags_Selected; ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0.0f, (row_height - ImGui::GetTextLineHeight()) / 2.0f}); - bool layer_open = ImGui::TreeNodeEx(s.layers[i].name.c_str(), flags); - ImGui::PopStyleVar(); + bool layer_open = ImGui::TreeNodeEx(current_layer.name.c_str(), flags); if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) { if (io.KeyCtrl) { if (selected) @@ -601,7 +657,7 @@ namespace K::UI { ImGuiDragDropFlags_SourceNoDisableHover | ImGuiDragDropFlags_SourceNoHoldToOpenOthers; if (!no_selection) source_flags |= ImGuiDragDropFlags_SourceNoPreviewTooltip; if ((no_selection || s.selected.contains(i)) && ImGui::BeginDragDropSource(source_flags)) { - if (s.selected.empty()) ImGui::Text("Swap with #%u %s", i, s.layers[i].name.c_str()); + if (s.selected.empty()) ImGui::Text("Swap with #%u %s", i, current_layer.name.c_str()); ImGui::SetDragDropPayload("K_COMP_REORDER", &i, sizeof(u32)); ImGui::EndDragDropSource(); } @@ -610,9 +666,9 @@ namespace K::UI { after = ImGui::GetMousePos().y - ImGui::GetWindowPos().y > ImGui::GetCursorPosY() - row_height / 2.0f; if (after) - ImGui::SetTooltip("After #%u %s", i, s.layers[i].name.c_str()); + ImGui::SetTooltip("After #%u %s", i, current_layer.name.c_str()); else - ImGui::SetTooltip("Before #%u %s", i, s.layers[i].name.c_str()); + ImGui::SetTooltip("Before #%u %s", i, current_layer.name.c_str()); } if (const ImGuiPayload *payload = ImGui::AcceptDragDropPayload("K_COMP_REORDER")) { @@ -624,11 +680,11 @@ namespace K::UI { ImGui::TableSetColumnIndex(2); ImGui::SetNextItemWidth(-FLT_MIN); - if (ImGui::BeginCombo("##Blending", Graphics::BlendingToString[s.layers[i].mode])) { + if (ImGui::BeginCombo("##Blending", Graphics::BlendingToString[current_layer.mode])) { for (i32 b = Graphics::K_V_AlphaOver; b != Graphics::K_V_Count; b++) { - const bool is_selected = (static_cast(b) == s.layers[i].mode); + const bool is_selected = (static_cast(b) == current_layer.mode); if (ImGui::Selectable(Graphics::BlendingToString[b], is_selected)) - s.layers[i].mode = static_cast(b); + current_layer.mode = static_cast(b); if (is_selected) ImGui::SetItemDefaultFocus(); } @@ -653,23 +709,8 @@ namespace K::UI { f32 init_y = ImGui::GetCursorScreenPos().y; - // do stuff here - static u32 pos = 20, pos_old = pos; - - f32 pos_view = static_cast(pos) / static_cast(s.frame_max + 1); - if (pos_view >= view_left && pos_view < view_right) { - ImGui::Dummy({(pos_view - view_left) / view_amt * view_width - 5.0f, 0.0f}); - ImGui::SameLine(); - ImGui::Button("kf", {0.0f, row_height * .8f}); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - pos_old = pos; - } - if (ImGui::IsItemActive()) { - pos = std::clamp(i64(pos_old) + - i64(std::round(delta_x * (view_amt * static_cast(s.frame_max + 1)))), 0L, i64(s.frame_max) - ); - } - } + ImGui::Dummy({TimelineFrameToScreenView(view_left, view_amt, view_width, current_layer.in, s.frame_max) + tl_init_pos.x, 0.0f}); + 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), view_height}); ImGui::SetCursorScreenPos(ImVec2{tl_init_pos.x, init_y} - style.CellPadding); ImGui::InvisibleButton("##TL_BG", ImVec2{ view_width + style.CellPadding.x * 2, row_height + style.CellPadding.y}); @@ -681,7 +722,7 @@ namespace K::UI { if (layer_open) { i32 j = 0; - for (auto &u: s.layers[i].track.uniforms) { + for (auto &u: current_layer.track.uniforms) { ImGui::PushID(j); ImGui::TableNextRow(ImGuiTableRowFlags_None, row_height); ImGui::TableSetColumnIndex(0); @@ -699,8 +740,9 @@ namespace K::UI { } ImGui::EndDragDropTarget(); } + auto& connected_v = u.second.connection.p->inputs_fed[u.second.connection.index]; - std::visit([&dragging_on_socket, &drag_source, &s, &style](auto &&arg) { + std::visit([&u, &dragging_on_socket, &drag_source, &s, &style](auto &&arg) { using T = std::decay_t; if constexpr (std::is_same_v) { if (ImGui::IsItemActive()) @@ -710,25 +752,36 @@ namespace K::UI { ImVec2{ImGui::GetFrameHeight() / 2, -ImGui::GetFrameHeight() / 2 - style.ItemInnerSpacing.y}); - } else if (ImGui::IsItemActive()) { - dragging_on_socket = true; - drag_source = ImGui::GetCursorScreenPos() + ImVec2{ImGui::GetFrameHeight() / 2, + } + else if (ImGui::IsItemActive()) { + if (ImGui::IsKeyPressed(ImGuiKey_LeftShift)) { + s.plugboard.nodes.push_back(PlugboardGraph::MakeInstance(PlugboardNodes::Chain)); + auto it = s.plugboard.nodes.rbegin(); + u.second.connection.p->connect(u.second.connection.index, &*it, 0); + it->connect(0, &s.plugboard.in_instance, 0); + // what happens to atomicity? though taking address is safe since we use a list now + } + else { + dragging_on_socket = true; + drag_source = ImGui::GetCursorScreenPos() + ImVec2{ImGui::GetFrameHeight() / 2, -ImGui::GetFrameHeight() / 2 - style.ItemInnerSpacing.y}; + } } - }, u.second.connection.p->inputs_fed[u.second.connection.index]); + }, connected_v); ImGui::TableSetColumnIndex(1); - ImGui::TreeNodeEx(u.first.c_str(), - ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_Leaf | - ImGuiTreeNodeFlags_NoTreePushOnOpen); + auto u_flags = ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_FramePadding; + if (connected_v.index() != 1) + u_flags |= ImGuiTreeNodeFlags_Leaf; + bool uniform_open = ImGui::TreeNodeEx(u.first.c_str(),u_flags); ImGui::TableSetColumnIndex(2); ImGui::SetNextItemWidth(-FLT_MIN); - bool plugged = u.second.connection.p->inputs_fed[u.second.connection.index].index() == 1; - if (!plugged) - std::visit([](auto &&arg) { + + if (connected_v.index() == 0) { + std::visit([](auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v::type>) ImGui::DragFloat("##", &arg, 0.005f); @@ -740,21 +793,73 @@ namespace K::UI { ImGui::DragFloat2("##", &arg.x, 0.005f); else if constexpr (std::is_same_v::type>) ImGui::DragFloat3("##", &arg.x, 0.005f); - }, std::get<0>(u.second.connection.p->inputs_fed[u.second.connection.index])); - + }, std::get<0>(connected_v)); + } ImGui::TableSetColumnIndex(3); ImGui::TableSetColumnIndex(4); + auto p = ImGui::GetCursorScreenPos(); + + // draw on tl + + ImGui::SetCursorScreenPos(p); + ImGui::InvisibleButton("##TL_BG", ImVec2{ view_width + style.CellPadding.x * 2, row_height + style.CellPadding.y}); if (ImGui::IsItemActive()) { s.current_frame = TimelineScreenViewToFrame(view_left, view_amt, view_width, std::clamp(ImGui::GetMousePos().x - tl_init_pos.x, 0.0f, view_width), s.frame_max); } + ImGui::TableSetColumnIndex(0); + if (uniform_open && (connected_v.index() == 1) && std::get<1>(connected_v).p->node == &PlugboardNodes::Chain) { + ImGui::TableNextRow(ImGuiTableRowFlags_None, row_height); + ImGui::TableSetColumnIndex(0); + ImGui::TableSetColumnIndex(1); + ImGui::Indent(); + ImGui::TreeNodeEx(std::get<1>(connected_v).p->node->name.c_str(), + ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_FramePadding); + ImGui::TableSetColumnIndex(2); + auto *m = std::any_cast>(&std::get<1>(connected_v).p->extra); + f32 v = std::get::type>(PlugboardGraph::Eval(s, std::get<1>(connected_v))), copy = v; + ImGui::DragFloat("##value", &v); + if (v != copy) { + auto l = m->lower_bound(s.current_frame); + auto interp = l == m->end() ? PlugboardNodes::K_I_Linear : l->second.interp.interp; + m->insert_or_assign(s.current_frame, PlugboardNodes::ChainSegment{ + .value = v, + .interp = { + .interp = interp + } + }); + } + ImGui::TableSetColumnIndex(3); + ImGui::Text("%lu", m->size()); + ImGui::TableSetColumnIndex(4); + ImVec2 begin_tl = ImGui::GetCursorScreenPos(); + static u32 drag_old{}; + for (auto& [k, segment] : *m) + if (TimelineFrameInView(view_left, view_right, k, s.frame_max)) { + ImGui::SetCursorScreenPos({TimelineFrameToScreenView(view_left, view_amt, view_width, k, s.frame_max) + begin_tl.x - 2.5f, begin_tl.y}); + ImGui::Button("k", {0.0f, row_height * .8f}); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + drag_old = k; + } + if (ImGui::IsItemActive()) { + // ... + } + } + + ImGui::SetCursorScreenPos(begin_tl); + ImGui::InvisibleButton("##TL_BG", ImVec2{ view_width + style.CellPadding.x * 2, row_height + style.CellPadding.y}); + if (ImGui::IsItemActive()) { + s.current_frame = TimelineScreenViewToFrame(view_left, view_amt, view_width, std::clamp(ImGui::GetMousePos().x - tl_init_pos.x, 0.0f, view_width), s.frame_max); + } + } + ImGui::PopID(); j++; } ImGui::TreePop(); } else - for (auto &u: s.layers[i].track.uniforms) + for (auto &u: current_layer.track.uniforms) std::visit([table_left, &s](auto &&arg) { using T = std::decay_t; if constexpr (std::is_same_v) @@ -762,9 +867,10 @@ namespace K::UI { {table_left, ImGui::GetCursorScreenPos().y - row_height / 2.0f}); }, u.second.connection.p->inputs_fed[u.second.connection.index]); + + ImGui::PopStyleVar(); // FramePadding ImGui::PopID(); } -// ImGui::PopStyleVar(); if (move_from != -1 && move_to != -1) { if (no_selection) @@ -796,15 +902,16 @@ namespace K::UI { ImGui::SetCursorScreenPos(nodes_begin); if (ImGui::BeginChild("Overlay", {table_left - ImGui::GetWindowPos().x, 0.0f}, false, ImGuiWindowFlags_NoInputs)) { + auto *window = ImGui::GetCurrentWindow(); for (auto &[_, link]: s.plugboard.links_pos) for (auto &sink: link.sinks) - ImGui::GetCurrentWindow()->DrawList->AddBezierCubic(link.source, + window->DrawList->AddBezierCubic(link.source, {link.source.x + 60.0f, link.source.y}, {sink.x - 60.0f, sink.y}, sink, 0xFFFFFFFF, 2.0f); if (dragging_on_socket) - ImGui::GetCurrentWindow()->DrawList->AddLine(ImGui::GetMousePos(), drag_source, 0xFFFFFFFF, 2.0f); + window->DrawList->AddLine(ImGui::GetMousePos(), drag_source, 0xFFFFFFFF, 2.0f); } ImGui::EndChild(); @@ -833,6 +940,7 @@ namespace K::UI { static String name{}; if (ImGui::Button("Add Uniform") && !name.empty() && !isdigit(name[0]) && !(name[0] == 'f' && name.length() == 1)) { s.layers[s.active].track.add_uniform(name, ShaderGraph::expand_type(static_cast(type_current))); + s.layers[s.active].track.expose_uniform(s, name); } ImGui::SameLine(); struct TextFilters { static int VariableName(ImGuiInputTextCallbackData *data) { @@ -869,19 +977,7 @@ namespace K::UI { ImGui::TableNextColumn(); ImGui::SetNextItemWidth(-FLT_MIN); if (it->second.connection.p->inputs_fed[it->second.connection.index].index() == 0) - std::visit([](auto&& arg) { - using T = std::decay_t; - if constexpr (std::is_same_v::type>) - ImGui::DragFloat("##", &arg, 0.005f); - else if constexpr (std::is_same_v::type>) - ImGui::DragInt("##", &arg, 0.005f); - else if constexpr (std::is_same_v::type>) - ImGui::DragFloat4("##", &arg.r, 0.005f); - else if constexpr (std::is_same_v::type>) - ImGui::DragFloat2("##", &arg.x, 0.005f); - else if constexpr (std::is_same_v::type>) - ImGui::DragFloat3("##", &arg.x, 0.005f); - }, std::get<0>(it->second.connection.p->inputs_fed[it->second.connection.index])); + DrawPlugboardVariableEditWidget(std::get<0>(it->second.connection.p->inputs_fed[it->second.connection.index])); ImGui::TableNextColumn(); if (ImGui::Button("X")) { bgfx::destroy(it->second.handle); @@ -900,6 +996,42 @@ namespace K::UI { ImGui::End(); } + f64 GetCubicUniqueReal(f64 a, f64 b, f64 c, f64 d) { + auto accept = [](f64 t){ return 0.0 <= t && t <= 1.0; }; + + f64 a1 = b/a, a2 = c/a, a3 = d/a; + f64 Q = (a1 * a1 - 3.0 * a2) / 9.0, + R = (2.0 * a1 * a1 * a1 - 9.0 * a1 * a2 + 27.0 * a3) / 54.0, + Qcubed = Q * Q * Q, + D = Qcubed - R * R; + + if (D >= 0) { + f64 theta = acos(R / sqrt(Qcubed)); + f64 sqrtQ = sqrt(Q); + f64 r1 = -2.0 * sqrtQ * cos( theta / 3.0) - a1 / 3.0, + r2 = -2.0 * sqrtQ * cos((theta + 2.0 * std::numbers::pi) / 3.0) - a1 / 3.0, + r3 = -2.0 * sqrtQ * cos((theta + 4.0 * std::numbers::pi) / 3.0) - a1 / 3.0; + return accept(r1) ? r1 : (accept(r2) ? r2 : r3); + } + else { + f64 e = std::pow(std::sqrt(-D) + std::abs(R), 1.0 / 3.0); + if (R > 0.0) + e = -e; + return (e + Q / e) - a1 / 3.; + } + } + f64 CubicBezier(f64 a, f64 b, f64 c, f64 d, f64 t) { + f64 t2 = t * t, t3 = t2 * t, mt = 1.0-t, mt2 = mt * mt, mt3 = mt2 * mt; + return a*mt3 + b*3.0*mt2*t + c*3.0*mt*t2 + d*t3; + } + f64 sb233asdf(f64 p2x, f64 p2y, f64 p3x, f64 p3y, f64 x) { + f64 a = 0.0f, b = p2x, + c = p3x, d = 1.0; + + f64 t = GetCubicUniqueReal(-a+3.0*b-3.0*c+d, 3.0*a-6.0*b+3.0*c, -3.0*a+3.0*b,a-x); + return CubicBezier(0.0, p2y, p3y, 1.0, t); + } + void Interpolation(CompState& s) { if (ImGui::Begin("Interpolation", &draw_interpolation, ImGuiWindowFlags_NoScrollbar)) { ImGuiIO& io = ImGui::GetIO(); @@ -927,7 +1059,21 @@ namespace K::UI { 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, 15.0f}, 0xFFFFFFFF, "1.0"); + dl->AddText(tp4 + ImVec2{-30.0f, 10.0f}, 0xFFFFFFFF, "1.0"); + + f32 x = std::clamp((ImGui::GetMousePos().x - pos.x) / w, 0.0f, 1.0f); + f32 y = sb233asdf(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); @@ -959,16 +1105,9 @@ namespace K::UI { ImGui::End(); } - void Properties(CompState& s) { - if (ImGui::Begin("Properties", &draw_properties)) { - - } - ImGui::End(); - } - void Assets(CompState& s) { if (ImGui::Begin("Assets", &draw_assets)) { - + ImGui::Text("mmoker"); } ImGui::End(); } @@ -988,7 +1127,6 @@ namespace K::UI { if (draw_shader) Shader(s); if (draw_comp) Composition(s); if (draw_interpolation) Interpolation(s); - if (draw_properties) Properties(s); if (draw_assets) Assets(s); if (save_called && ready_frame == frame) { diff --git a/Keishiki/VisualTrack.cpp b/Keishiki/VisualTrack.cpp index 5ed8b6b..c952ae7 100644 --- a/Keishiki/VisualTrack.cpp +++ b/Keishiki/VisualTrack.cpp @@ -151,7 +151,7 @@ namespace K { case bgfx::RendererType::Vulkan: s = "./ext/bgfx/cmake/bgfx/shaderc " "-f temp.frag " "--type fragment " - "--platform windows " + "--platform linux " "--profile " "spirv" " ""--varyingdef temp.varying.def.sc " "-i ./ " "-o shaders/" "spirv" "/temp.frag.bin"; break; diff --git a/Keishiki/include/Common.h b/Keishiki/include/Common.h index c4ef703..69c9e26 100644 --- a/Keishiki/include/Common.h +++ b/Keishiki/include/Common.h @@ -25,7 +25,7 @@ namespace K { template using Vector = std::vector; template using Dict = std::unordered_map; // to be replaced - inline void LogBase(const String& s, u8 level) { + inline void LogBase(const std::string_view& s, u8 level) { static const char *levels[] = { "Info", "Warning", @@ -33,10 +33,10 @@ namespace K { }; level > 1 ? std::cerr : std::cout << "[" << levels[level] << "] Keishiki: " << s << std::endl; } - inline void Log(const String& s) { + inline void Log(const std::string_view& s) { LogBase(s, 0); } - inline void LogError(const String& s) { + inline void LogError(const std::string_view& s) { LogBase(s, 2); } diff --git a/Keishiki/include/Keishiki.h b/Keishiki/include/Keishiki.h index fc00138..217bec2 100644 --- a/Keishiki/include/Keishiki.h +++ b/Keishiki/include/Keishiki.h @@ -16,6 +16,7 @@ namespace K { VisualTrack track; String name; Graphics::Blending mode; + u64 in, out; }; struct CompState { diff --git a/Keishiki/include/PlugboardGraph.h b/Keishiki/include/PlugboardGraph.h index c83bec0..5c80496 100644 --- a/Keishiki/include/PlugboardGraph.h +++ b/Keishiki/include/PlugboardGraph.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace K::PlugboardGraph { enum Type { @@ -60,17 +61,18 @@ namespace K::PlugboardGraph { Type type; }; + struct NodeInstance; struct Node { String name; // will likely get SSO for most stuff Vector in; Vector out; - Vector::type(const CompState&, const Vector::type>&)>> fetch; // maybe change to a function pointer later - // todo investigate how you might wanna jit this instead of evaluating a tree for each output at runtime esp. when a project is set for rendering + Vector::type(*)(const CompState&, const NodeInstance&)> fetch; // todo investigate how you might wanna jit this instead of evaluating a tree for each output at runtime esp. when a project is set for rendering + void (*special_draw)(NodeInstance&); + void (*extra_setup)(NodeInstance&); }; - struct NodeInstance; struct ConnectInfo { NodeInstance *p; // NOTE: so NodeInstances must be stored in a list, otherwise vector realloc leads to UAF @@ -81,7 +83,7 @@ namespace K::PlugboardGraph { struct ConnectInfoHasher { std::size_t operator()(const ConnectInfo& k) const { - return std::hash()(k.p); + return std::hash()(k.p) ^ std::hash()(k.index); } }; @@ -93,6 +95,8 @@ namespace K::PlugboardGraph { ImVec2 pos, old_pos; + std::any extra; + void connect(u32 input_index, NodeInstance *to, u32 to_out_index) { inputs_fed[input_index] = ConnectInfo{to, to_out_index}; to->outputs_going[to_out_index].push_back({this, input_index}); diff --git a/Keishiki/include/PlugboardNodes.h b/Keishiki/include/PlugboardNodes.h index 74431b5..e04fe89 100644 --- a/Keishiki/include/PlugboardNodes.h +++ b/Keishiki/include/PlugboardNodes.h @@ -1,15 +1,607 @@ #pragma once #include "Common.h" #include "PlugboardGraph.h" +#include +#include +#include +#include + +namespace { + template + static K::PlugboardGraph::T_Map::type GetNodeInputArg(const K::CompState& s, const K::PlugboardGraph::NodeInstance& n, u32 index) { + bool good; + typename K::PlugboardGraph::T_Map::type v = std::get::type>(K::PlugboardGraph::ConvertValue(std::visit([&s](auto&& arg) { + using U = std::decay_t; + if constexpr (std::is_same_v) + return K::PlugboardGraph::Eval(s, arg); + else + return arg; + }, n.inputs_fed[index]), type, good)); + if (!good) + K::LogError("Type mismatch on plugboard evaluation!"); + + return v; + } + + // Assume cubic injects over [0, 1] + f64 GetCubicUniqueReal(f64 a, f64 b, f64 c, f64 d) { + auto accept = [](f64 t){ return 0.0 <= t && t <= 1.0; }; + + f64 a1 = b/a, a2 = c/a, a3 = d/a; + f64 Q = (a1 * a1 - 3.0 * a2) / 9.0, + R = (2.0 * a1 * a1 * a1 - 9.0 * a1 * a2 + 27.0 * a3) / 54.0, + Qcubed = Q * Q * Q, + D = Qcubed - R * R; + + if (D >= 0) { + f64 theta = acos(R / sqrt(Qcubed)); + f64 sqrtQ = sqrt(Q); + f64 r1 = -2.0 * sqrtQ * cos(theta / 3.0) - a1 / 3.0, + r2 = -2.0 * sqrtQ * cos((theta + 2.0 * std::numbers::pi) / 3.0) - a1 / 3.0, + r3 = -2.0 * sqrtQ * cos((theta + 4.0 * std::numbers::pi) / 3.0) - a1 / 3.0; + return accept(r1) ? r1 : (accept(r2) ? r2 : r3); + } + else { + f64 e = std::pow(std::sqrt(-D) + std::abs(R), 1.0 / 3.0); + if (R > 0.0) + e = -e; + return (e + Q / e) - a1 / 3.; + } + } + + f64 CubicBezier(f64 a, f64 b, f64 c, f64 d, f64 t) { + f64 t2 = t * t, t3 = t2 * t, mt = 1.0-t, mt2 = mt * mt, mt3 = mt2 * mt; + return a*mt3 + b*3.0*mt2*t + c*3.0*mt*t2 + d*t3; + } +} namespace K::PlugboardNodes { - PlugboardGraph::Node Sine = { - .name = "Sine", - .in = { {"In", PlugboardGraph::T_Float } }, + PlugboardGraph::T_Map::type FetchAdd(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 x = GetNodeInputArg(s, n, 0), + y = GetNodeInputArg(s, n, 1); + return x + y; + } - .out = { { "Out", PlugboardGraph::T_Float } }, - .fetch = {[](const CompState&, const Vector::type>& arg) { - return std::sin(std::get(arg[0])); - }} + PlugboardGraph::Node Add = { + .name = "Add", + .in = { {"a", PlugboardGraph::T_Float }, {"b", PlugboardGraph::T_Float } }, + .out = { { "a+b", PlugboardGraph::T_Float } }, + .fetch = { FetchAdd } }; + + PlugboardGraph::T_Map::type FetchNegate(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 x = GetNodeInputArg(s, n, 0); + return -x; + } + + PlugboardGraph::Node Negate = { + .name = "Negate", + .in = { {"a", PlugboardGraph::T_Float } }, + .out = { { "-a", PlugboardGraph::T_Float } }, + .fetch = { FetchNegate } + }; + + PlugboardGraph::T_Map::type FetchSubtract(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 x = GetNodeInputArg(s, n, 0), + y = GetNodeInputArg(s, n, 1); + return x-y; + } + + PlugboardGraph::Node Subtract = { + .name = "Subtract", + .in = { {"a", PlugboardGraph::T_Float }, {"b", PlugboardGraph::T_Float } }, + .out = { { "a-b", PlugboardGraph::T_Float } }, + .fetch = { FetchSubtract } + }; + + PlugboardGraph::T_Map::type FetchMultiply(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 x = GetNodeInputArg(s, n, 0), + y = GetNodeInputArg(s, n, 1); + return x*y; + } + + PlugboardGraph::Node Multiply = { + .name = "Multiply", + .in = { {"a", PlugboardGraph::T_Float }, {"b", PlugboardGraph::T_Float } }, + .out = { { "a*b", PlugboardGraph::T_Float } }, + .fetch = {FetchMultiply } + }; + + PlugboardGraph::T_Map::type FetchDivide(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 x = GetNodeInputArg(s, n, 0), + y = GetNodeInputArg(s, n, 1); + return x/y; + } + + PlugboardGraph::Node Divide = { + .name = "Divide", + .in = { {"a", PlugboardGraph::T_Float }, {"b", PlugboardGraph::T_Float } }, + .out = { { "a/b", PlugboardGraph::T_Float } }, + .fetch = { FetchDivide } + }; + + PlugboardGraph::T_Map::type FetchSign(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 x = GetNodeInputArg(s, n, 0); + return std::signbit(x) ? -1.0f : 1.0f; + } + + PlugboardGraph::Node Sign = { + .name = "Sign", + .in = { {"a", PlugboardGraph::T_Float } }, + .out = { { "sgn(a)", PlugboardGraph::T_Float } }, + .fetch = { FetchSign } + }; + + PlugboardGraph::T_Map::type FetchSin(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 x = GetNodeInputArg(s, n, 0); + return std::sin(x); + } + + PlugboardGraph::Node Sin = { + .name = "Sin", + .in = { {"a (rad)", PlugboardGraph::T_Float } }, + .out = { { "sin(a)", PlugboardGraph::T_Float } }, + .fetch = { FetchSin } + }; + + + PlugboardGraph::T_Map::type FetchCos(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 x = GetNodeInputArg(s, n, 0); + return std::cos(x); + } + + PlugboardGraph::Node Cos = { + .name = "Cos", + .in = { {"a (rad)", PlugboardGraph::T_Float } }, + .out = { { "cos(a)", PlugboardGraph::T_Float } }, + .fetch = { FetchCos } + }; + + + PlugboardGraph::T_Map::type FetchTan(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 x = GetNodeInputArg(s, n, 0); + return std::tan(x); + } + + PlugboardGraph::Node Tan = { + .name = "Tan", + .in = { {"a (rad)", PlugboardGraph::T_Float } }, + .out = { { "tan(a)", PlugboardGraph::T_Float } }, + .fetch = { FetchTan } + }; + + + PlugboardGraph::T_Map::type FetchArcsin(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 x = GetNodeInputArg(s, n, 0); + return std::asin(x); + } + + PlugboardGraph::Node Arcsin = { + .name = "Arcsin", + .in = { {"a", PlugboardGraph::T_Float } }, + .out = { { "arcsin(a) (rad)", PlugboardGraph::T_Float } }, + .fetch = { FetchArcsin } + }; + + + PlugboardGraph::T_Map::type FetchArccos(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 x = GetNodeInputArg(s, n, 0); + return std::acos(x); + } + + PlugboardGraph::Node Arccos = { + .name = "Arccos", + .in = { {"a", PlugboardGraph::T_Float } }, + .out = { { "arccos(a) (rad)", PlugboardGraph::T_Float } }, + .fetch = { FetchArccos } + }; + + PlugboardGraph::T_Map::type FetchArctan(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 x = GetNodeInputArg(s, n, 0); + return std::atan(x); + } + + PlugboardGraph::Node Arctan = { + .name = "Arctan", + .in = { {"a", PlugboardGraph::T_Float } }, + .out = { { "arctan(a) (rad)", PlugboardGraph::T_Float } }, + .fetch = { FetchArctan } + }; + + PlugboardGraph::T_Map::type FetchAtan2(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 y = GetNodeInputArg(s, n, 0), + x = GetNodeInputArg(s, n, 1); + return std::atan2(y, x); + } + + PlugboardGraph::Node Atan2 = { + .name = "Atan2", + .in = { {"y", PlugboardGraph::T_Float }, {"x", PlugboardGraph::T_Float } }, + .out = { { "atan2(y, x) (rad)", PlugboardGraph::T_Float } }, + .fetch = { FetchAtan2 } + }; + + PlugboardGraph::T_Map::type FetchMinimum(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 y = GetNodeInputArg(s, n, 0), + x = GetNodeInputArg(s, n, 1); + return std::min(y, x); + } + + PlugboardGraph::Node Minimum = { + .name = "Minimum", + .in = { {"a", PlugboardGraph::T_Float }, {"b", PlugboardGraph::T_Float } }, + .out = { { "min(a, b)", PlugboardGraph::T_Float } }, + .fetch = { FetchMinimum } + }; + + PlugboardGraph::T_Map::type FetchMaximum(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 y = GetNodeInputArg(s, n, 0), + x = GetNodeInputArg(s, n, 1); + return std::max(y, x); + } + + PlugboardGraph::Node Maximum = { + .name = "Maximum", + .in = { {"a", PlugboardGraph::T_Float }, {"b", PlugboardGraph::T_Float } }, + .out = { { "max(a, b)", PlugboardGraph::T_Float } }, + .fetch = {FetchMaximum } + }; + + PlugboardGraph::T_Map::type FetchPower(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 x = GetNodeInputArg(s, n, 0), + y = GetNodeInputArg(s, n, 1); + return std::pow(x, y); + } + + PlugboardGraph::Node Power = { + .name = "Power", + .in = { {"a", PlugboardGraph::T_Float }, {"b", PlugboardGraph::T_Float } }, + .out = { { "a^b", PlugboardGraph::T_Float } }, + .fetch = {FetchPower } + }; + + PlugboardGraph::T_Map::type FetchSquareRoot(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 x = GetNodeInputArg(s, n, 0); + return std::sqrt(x); + } + + PlugboardGraph::Node SquareRoot = { + .name = "Square Root", + .in = { {"a", PlugboardGraph::T_Float } }, + .out = { { "sqrt(a)", PlugboardGraph::T_Float } }, + .fetch = { FetchSquareRoot } + }; + + PlugboardGraph::T_Map::type FetchNaturalLogarithm(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 x = GetNodeInputArg(s, n, 0); + return std::log(x); + } + + PlugboardGraph::Node NaturalLogarithm = { + .name = "Natural Logarithm", + .in = { {"a", PlugboardGraph::T_Float } }, + .out = { { "log(a)", PlugboardGraph::T_Float } }, + .fetch = {FetchNaturalLogarithm } + }; + + PlugboardGraph::T_Map::type FetchAbsoluteValue(const CompState& s, const PlugboardGraph::NodeInstance& n) { + f32 x = GetNodeInputArg(s, n, 0); + return std::log(x); + } + + PlugboardGraph::Node AbsoluteValue = { + .name = "Absolute Value", + .in = { {"a", PlugboardGraph::T_Float } }, + .out = { { "|a|", PlugboardGraph::T_Float } }, + .fetch = { FetchAbsoluteValue } + }; + + enum K_Interpolation { + K_I_CubicBezier, + K_I_InConstant, + K_I_OutConstant, + K_I_Linear, + K_I_Step, + K_I_SmoothStep, + K_I_InQuadratic, + K_I_OutQuadratic, + K_I_InOutQuadratic, + K_I_OutInQuadratic, + K_I_InCubic, + K_I_OutCubic, + K_I_InOutCubic, + K_I_OutInCubic, + K_I_InQuartic, + K_I_OutQuartic, + K_I_InOutQuartic, + K_I_OutInQuartic, + K_I_InQuintic, + K_I_OutQuintic, + K_I_InOutQuintic, + K_I_OutInQuintic, + K_I_InSine, + K_I_OutSine, + K_I_InOutSine, + K_I_OutInSine, + K_I_InExponential, + K_I_OutExponential, + K_I_InOutExponential, + K_I_OutInExponential, + K_I_InCircular, + K_I_OutCircular, + K_I_InOutCircular, + K_I_OutInCircular, + K_I_InElastic, + K_I_OutElastic, + K_I_InOutElastic, + K_I_OutInElastic, + K_I_InBack, + K_I_OutBack, + K_I_InOutBack, + K_I_OutInBack, + K_I_InBounce, + K_I_OutBounce, + K_I_InOutBounce, + K_I_OutInBounce, + K_I_Count + }; + + + const char *K_Interpolation_Names[] { + "Cubic Bezier", + "In Constant", + "Out Constant", + "Linear", + "Step", + "Smooth Step", + "In Quadratic", + "Out Quadratic", + "In Out Quadratic", + "Out In Quadratic", + "In Cubic", + "Out Cubic", + "In Out Cubic", + "Out In Cubic", + "In Quartic", + "Out Quartic", + "In Out Quartic", + "Out In Quartic", + "In Quintic", + "Out Quintic", + "In Out Quintic", + "Out In Quintic", + "In Sine", + "Out Sine", + "In Out Sine", + "Out In Sine", + "In Exponential", + "Out Exponential", + "In Out Exponential", + "Out In Exponential", + "In Circular", + "Out Circular", + "In Out Circular", + "Out In Circular", + "In Elastic", + "Out Elastic", + "In Out Elastic", + "Out In Elastic", + "In Back", + "Out Back", + "In Out Back", + "Out In Back", + "In Bounce", + "Out Bounce", + "In Out Bounce", + "Out In Bounce", + "Invalid" + }; + + struct InterpolationExtra { + K_Interpolation interp; + f32 v[4]; + }; + + 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 = 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(CubicBezier(0.0f, p2y, p3y, 1.0f, t)); + } + default: + return {}; + } + } + + PlugboardGraph::T_Map::type FetchInterpolation(const CompState& s, const PlugboardGraph::NodeInstance& n) { + auto *e = std::any_cast(&n.extra); + auto type = e->interp; + f32 x = GetNodeInputArg(s, n, 0); + return EvalInterpolation(x, *e); + } + + void DrawInterpolation(PlugboardGraph::NodeInstance& n) { + auto *v = std::any_cast(&n.extra); + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::BeginCombo("##Interp", K_Interpolation_Names[v->interp], ImGuiComboFlags_None)) { + for (u32 i = 0; i < K_Interpolation::K_I_Count; i++) { + const bool is_selected = (v->interp == i); + if (ImGui::Selectable(K_Interpolation_Names[i], is_selected)) + v->interp = static_cast(i); + + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + } + + void SetUpInterpolation(PlugboardGraph::NodeInstance& n) { + n.extra = InterpolationExtra{}; + } + + PlugboardGraph::Node Interpolation = { + .name = "Interpolation", + .in = { {"t", PlugboardGraph::T_Float } }, + .out = { { "Value", PlugboardGraph::T_Float } }, + .fetch = { FetchInterpolation }, + .special_draw = DrawInterpolation, + .extra_setup = SetUpInterpolation + }; + + struct ChainSegment { + f32 value; + InterpolationExtra interp; + }; + + PlugboardGraph::T_Map::type FetchChain(const CompState& s, const PlugboardGraph::NodeInstance& n) { + u32 frame = GetNodeInputArg(s, n, 0); + + auto *v = std::any_cast>(&n.extra); + if (v->empty()) + return 0.0f; + if (frame < v->begin()->first) + return v->begin()->second.value; + if (frame > v->rbegin()->first) + return v->rbegin()->second.value; + + auto nit = v->upper_bound(frame), it = std::prev(nit); + u32 x1 = it->first, x2 = nit->first; + f32 t = static_cast(frame - x1) / static_cast(x2 - x1); + f32 normalized_val = EvalInterpolation(t, it->second.interp); + + return it->second.value + normalized_val * (nit->second.value - it->second.value); + } + + void SetUpChain(PlugboardGraph::NodeInstance& n) { + n.extra = std::map{}; + } + + PlugboardGraph::Node Chain = { + .name = "Chain", + .in = { {"In", PlugboardGraph::T_Int } }, + .out = { { "Out", PlugboardGraph::T_Float } }, + .fetch = { FetchChain }, + .special_draw = nullptr, + .extra_setup = SetUpChain + }; + + auto Nodes = std::to_array({ + &Add, + &Subtract, + &Multiply, + &Divide, + &Negate, + + &NaturalLogarithm, + &SquareRoot, + &Power, + + &AbsoluteValue, + &Sign, + &Minimum, + &Maximum, + + &Sin, + &Cos, + &Tan, + &Arcsin, + &Arccos, + &Arctan, + &Atan2, + + &Interpolation + }); } diff --git a/TODO.md b/TODO.md index 6cd7da5..f9df6cd 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,6 @@ # NOW ## Chores - Blending modes -- Basic plugboard nodes - Node groups ## Compositor @@ -13,15 +12,14 @@ - Text (idea: index-based evaluation in plugboard) - Shapes (idea: embed glisp :mmtroll:, need to inquire -- still a lot of friction for simple shapes if we don't also get the glisp tools) - External data driving (json or something else?) -- Pre compose +- Pre-compose - Motion blur - Layer Groups (jokes -- can be completely UI side) ## UI -- Interpolation Editor init -- Timeline/Dope sheet init - Viewport gizmos (Layer-specific & nodes -- can separate into different tools?) - Node loop detection (separate DFS (extra work) or be smart in recursion) + - detect time-sensitive nodes in tree and display on timeline # Later ## Compositor