// // Created by martin on 11.02.22. // #include "AnimGraphEditor.h" #include "AnimGraphResource.h" #include "imnodes.h" using namespace AnimGraphCode; ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) { switch (socket_type) { case SocketType::SocketTypeAnimation: return ImNodesPinShape_QuadFilled; case SocketType::SocketTypeFloat: return ImNodesPinShape_CircleFilled; case SocketType::SocketTypeVec3: return ImNodesPinShape_TriangleFilled; case SocketType::SocketTypeQuat: return ImNodesPinShape_Triangle; case SocketType::SocketTypeBool: return ImNodesPinShape_Circle; default: break; } return ImNodesPinShape_Quad; } void AnimGraphEditorRenderSidebar(AnimNodeResource& node_resource) { ImGui::Text("[%s]", node_resource.m_type_name.c_str()); char node_name_buffer[256]; memset(node_name_buffer, 0, sizeof(node_name_buffer)); strncpy( node_name_buffer, node_resource.m_name.c_str(), std::min(node_resource.m_name.size(), sizeof(node_name_buffer))); if (ImGui::InputText("Name", node_name_buffer, sizeof(node_name_buffer))) { node_resource.m_name = node_name_buffer; } int num_properties = node_resource.m_socket_accessor->m_properties.size(); for (int i = 0; i < num_properties; i++) { Socket& property = node_resource.m_socket_accessor->m_properties[i]; if (property.m_type == SocketType::SocketTypeFloat) { ImGui::SliderFloat( property.m_name.c_str(), reinterpret_cast(property.m_value.ptr), -100.f, 100.f); } else if (property.m_type == SocketType::SocketTypeBool) { ImGui::Checkbox( property.m_name.c_str(), reinterpret_cast(property.m_value.ptr)); } else if (property.m_type == SocketType::SocketTypeString) { std::string* property_string = reinterpret_cast(property.m_value.ptr); char string_buf[256]; memset(string_buf, 0, sizeof(string_buf)); strncpy( string_buf, property_string->c_str(), std::min(property_string->size(), sizeof(string_buf))); if (ImGui::InputText( property.m_name.c_str(), string_buf, sizeof(string_buf))) { (*property_string) = string_buf; } } } } void AnimGraphEditorUpdate() { static AnimGraphResource graph_resource = AnimGraphResource(); ImGui::BeginMenuBar(); if (ImGui::Button("Save")) { graph_resource.saveToFile("editor_graph.json"); } if (ImGui::Button("Load")) { graph_resource.loadFromFile("editor_graph.json"); for (size_t i = 0, n = graph_resource.m_nodes.size(); i < n; i++) { const AnimNodeResource& node_resource = graph_resource.m_nodes[i]; ImNodes::SetNodeGridSpacePos( i, ImVec2(node_resource.m_position[0], node_resource.m_position[1])); } } if (ImGui::Button("Clear")) { graph_resource.clear(); } char graph_name_buffer[256]; memset(graph_name_buffer, 0, sizeof(graph_name_buffer)); strncpy( graph_name_buffer, graph_resource.m_name.c_str(), sizeof(graph_name_buffer)); if (ImGui::InputText("Name", graph_name_buffer, sizeof(graph_name_buffer))) { graph_resource.m_name = graph_name_buffer; } ImGui::EndMenuBar(); ImGui::Columns(2); // // Node editor canvas // ImNodes::BeginNodeEditor(); // Popup menu { const bool open_popup = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && ImNodes::IsEditorHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Right); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.f, 8.f)); if (!ImGui::IsAnyItemHovered() && open_popup) { ImGui::OpenPopup("add node"); } if (ImGui::BeginPopup("add node")) { const ImVec2 click_pos = ImGui::GetMousePosOnOpeningCurrentPopup(); std::string node_type_name = ""; if (ImGui::MenuItem("AnimSampler")) { node_type_name = "AnimSampler"; } if (ImGui::MenuItem("Blend2")) { node_type_name = "Blend2"; } if (ImGui::MenuItem("SpeedScale")) { node_type_name = "SpeedScale"; } if (node_type_name != "") { AnimNodeResource node_resource = AnimNodeResourceFactory(node_type_name); size_t node_id = graph_resource.m_nodes.size(); ImNodes::SetNodeScreenSpacePos(node_id, ImGui::GetMousePos()); graph_resource.m_nodes.push_back(node_resource); } ImGui::EndPopup(); } ImGui::PopStyleVar(ImGuiStyleVar_WindowPadding); } for (size_t i = 0, n = graph_resource.m_nodes.size(); i < n; i++) { AnimNodeResource& node_resource = graph_resource.m_nodes[i]; ImNodes::BeginNode(i); // Header ImNodes::BeginNodeTitleBar(); if (i == 0) { ImGui::TextUnformatted("Graph Outputs"); } else if (i == 1) { ImGui::TextUnformatted("Graph Inputs"); } else { ImGui::TextUnformatted(node_resource.m_type_name.c_str()); } ImNodes::EndNodeTitleBar(); // Inputs const std::vector& node_inputs = node_resource.m_socket_accessor->m_inputs; for (size_t j = 0, ni = node_inputs.size(); j < ni; j++) { const Socket& socket = node_inputs[j]; ImColor socket_color = ImColor(255, 255, 255, 255); if (socket.m_flags & SocketFlagAffectsTime) { socket_color = ImColor(255, 128, 128, 255); } ImNodes::BeginInputAttribute( GenerateInputAttributeId(i, j), sGetSocketShapeFromSocketType(socket.m_type), socket_color); ImGui::Text(socket.m_name.c_str()); ImNodes::PushAttributeFlag( ImNodesAttributeFlags_EnableLinkDetachWithDragClick); ImNodes::EndInputAttribute(); } // Outputs const std::vector& node_outputs = node_resource.m_socket_accessor->m_outputs; for (size_t j = 0, ni = node_outputs.size(); j < ni; j++) { const Socket& socket = node_outputs[j]; ImNodes::BeginOutputAttribute( GenerateOutputAttributeId(i, j), sGetSocketShapeFromSocketType(socket.m_type), ImColor(255, 255, 255, 255)); ImGui::Text(socket.m_name.c_str()); ImNodes::PushAttributeFlag( ImNodesAttributeFlags_EnableLinkDetachWithDragClick); ImNodes::EndInputAttribute(); } // Graph output node if (i == 0) { if (ImGui::Button("+Output")) { AnimNodeResource& graph_output_node = graph_resource.getGraphOutputNode(); static float bla = 0.f; std::string socket_name = "Output"; socket_name += std::to_string( graph_output_node.m_socket_accessor->m_inputs.size()); graph_output_node.m_socket_accessor->RegisterInput( socket_name, nullptr); } } else if (i == 1) { if (ImGui::Button("+Input")) { AnimNodeResource& graph_input_node = graph_resource.getGraphInputNode(); static float bla = 0.f; std::string socket_name = "Input"; socket_name += std::to_string( graph_input_node.m_socket_accessor->m_outputs.size()); graph_input_node.m_socket_accessor->RegisterOutput( socket_name, nullptr); } } // Save state in node resource ImVec2 node_pos = ImNodes::GetNodeGridSpacePos(i); node_resource.m_position[0] = node_pos[0]; node_resource.m_position[1] = node_pos[1]; ImNodes::EndNode(); // Ensure flags such as SocketFlagAffectsTime are properly set. node_resource.m_socket_accessor->UpdateFlags(); } for (size_t i = 0, n = graph_resource.m_connections.size(); i < n; i++) { const AnimGraphConnection& connection = graph_resource.m_connections[i]; int start_attr, end_attr; start_attr = GenerateOutputAttributeId( connection.m_source_node_index, connection.m_source_socket_index); end_attr = GenerateInputAttributeId( connection.m_target_node_index, connection.m_target_socket_index); ImNodes::Link(i, start_attr, end_attr); } ImNodes::EndNodeEditor(); // Handle newly created links. int start_attr, end_attr; if (ImNodes::IsLinkCreated(&start_attr, &end_attr)) { int node_start_id; int node_start_output_index; SplitOutputAttributeId( start_attr, &node_start_id, &node_start_output_index); int node_end_id; int node_end_input_index; SplitInputAttributeId(end_attr, &node_end_id, &node_end_input_index); AnimGraphConnection connection; connection.m_source_node_index = node_start_id; connection.m_source_socket_index = node_start_output_index; connection.m_target_node_index = node_end_id; connection.m_target_socket_index = node_end_input_index; graph_resource.m_connections.push_back(connection); } // Handle link detachements. int link_id = 0; if (ImNodes::IsLinkDestroyed(&link_id)) { graph_resource.m_connections.erase( graph_resource.m_connections.begin() + link_id); } int selected_nodes[ImNodes::NumSelectedNodes()]; ImNodes::GetSelectedNodes(selected_nodes); // // Sidebar // ImGui::NextColumn(); if (ImNodes::NumSelectedNodes() == 1) { if (selected_nodes[0] < graph_resource.m_nodes.size()) { AnimNodeResource& selected_node = graph_resource.m_nodes[selected_nodes[0]]; AnimGraphEditorRenderSidebar(selected_node); } } ImGui::Columns(1); }