// // Created by martin on 11.02.22. // #include "AnimGraphEditor.h" #include #include "3rdparty/imgui-node-editor/imgui_node_editor.h" #include "AnimGraphResource.h" #include "SkinnedMesh.h" #include "imgui.h" #include "imnodes.h" #include "misc/cpp/imgui_stdlib.h" static AnimGraphResource sGraphGresource = AnimGraphResource(); static bool sGraphLoadedThisFrame = false; ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) { switch (socket_type) { case SocketType::SocketTypeAnimation: return ImNodesPinShape_QuadFilled; case SocketType::SocketTypeInt: return ImNodesPinShape_CircleFilled; 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 NodeSocketEditor(Socket& socket) { int mode_current = static_cast(socket.m_type); ImGui::InputText("Name", &socket.m_name); if (ImGui::Combo( "Type", &mode_current, SocketTypeNames, sizeof(SocketTypeNames) / sizeof(char*))) { socket.m_type = static_cast(mode_current); } } void RemoveConnectionsForSocket( AnimGraphResource& graph_resource, AnimNodeResource& node_resource, Socket& socket) { std::vector::iterator iter = graph_resource.m_connections.begin(); while (iter != graph_resource.m_connections.end()) { // TODO adjust for refactor assert(false); // AnimGraphConnectionResource& connection = *iter; // if (connection.m_source_node == &node_resource // && connection.m_source_socket == &socket) { // iter = sGraphGresource.m_connections.erase(iter); // } else { // iter++; // } } } void SyncTrackEditor(SyncTrack* sync_track) { ImGui::SliderFloat("duration", &sync_track->m_duration, 0.001f, 10.f); ImGui::Text("Marker"); ImGui::SameLine(); ImGui::Text("%d", sync_track->m_num_intervals); ImGui::SameLine(); if (ImGui::Button("+")) { if (sync_track->m_num_intervals < cSyncTrackMaxIntervals) { sync_track->m_num_intervals++; } } ImGui::SameLine(); if (ImGui::Button("-")) { if (sync_track->m_num_intervals > 0) { sync_track->m_num_intervals--; } } ImGui::Text("Marker:"); for (int i = 0; i < sync_track->m_num_intervals; i++) { ImGui::Text("%2d:", i); ImGui::SameLine(); std::ostringstream marker_stream; marker_stream << i; ImGui::SliderFloat( marker_stream.str().c_str(), &sync_track->m_sync_markers[i], 0.f, 1.f); } if (ImGui::Button("Update Intervals")) { sync_track->CalcIntervals(); } } void SkinnedMeshWidget(SkinnedMesh* skinned_mesh) { if (ImGui::TreeNode("Bones")) { for (int i = 0; i < skinned_mesh->m_skeleton.num_joints(); i++) { ImGui::Text("%s", skinned_mesh->m_skeleton.joint_names()[i]); } ImGui::TreePop(); } ImGui::Text("Animations"); const char* items[255] = {0}; static int selected = -1; for (int i = 0; i < skinned_mesh->m_animations.size(); i++) { items[i] = skinned_mesh->m_animation_names[i].c_str(); } ImGui::Combo( "Animation", &selected, items, skinned_mesh->m_animations.size()); ImGui::Text("Sync Track"); if (selected >= 0 && selected < skinned_mesh->m_animations.size()) { SyncTrackEditor(&skinned_mesh->m_animation_sync_track[selected]); skinned_mesh->m_override_anim = selected; ImGui::Checkbox("Override Animation", &skinned_mesh->m_sync_track_override); if (skinned_mesh->m_sync_track_override) { ImGui::SliderFloat("Ratio", &skinned_mesh->m_override_ratio, 0.f, 1.f); ozz::animation::SamplingJob sampling_job; sampling_job.animation = skinned_mesh->m_animations[selected]; sampling_job.context = &skinned_mesh->m_sampling_context; sampling_job.ratio = skinned_mesh->m_override_ratio; sampling_job.output = make_span(skinned_mesh->m_local_matrices); if (!sampling_job.Run()) { ozz::log::Err() << "Error sampling animation." << std::endl; } } } } void AnimGraphEditorRenderSidebar( AnimGraphResource& graph_resource, 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::SocketTypeInt) { ImGui::InputInt( property.m_name.c_str(), reinterpret_cast(&property.m_value.int_value), 1); } else if (property.m_type == SocketType::SocketTypeFloat) { ImGui::SliderFloat( property.m_name.c_str(), reinterpret_cast(&property.m_value.float_value), -100.f, 100.f); } else if (property.m_type == SocketType::SocketTypeBool) { bool flag_value = property.GetValue(); if (ImGui::Checkbox( property.m_name.c_str(), &flag_value)) { property.SetValue(flag_value); } } else if (property.m_type == SocketType::SocketTypeString) { char string_buf[1024]; memset(string_buf, '\0', sizeof(string_buf)); memcpy( string_buf, property.m_value_string.c_str(), std::min( static_cast(1024), property.m_value_string.size() + 1)); if (ImGui::InputText( property.m_name.c_str(), string_buf, sizeof(string_buf))) { property.m_value_string = string_buf; } } } if (&node_resource == &graph_resource.getGraphOutputNode()) { ImGui::Text("Outputs"); // Graph outputs are the inputs of the output node! std::vector& outputs = node_resource.m_socket_accessor->m_inputs; std::vector::iterator iter = outputs.begin(); while (iter != outputs.end()) { Socket& output = *iter; ImGui::PushID(&output); NodeSocketEditor(output); if (ImGui::Button("X")) { RemoveConnectionsForSocket(graph_resource, node_resource, output); iter = outputs.erase(iter); } else { iter++; } ImGui::PopID(); } } if (&node_resource == &graph_resource.getGraphInputNode()) { ImGui::Text("Inputs"); // Graph inputs are the outputs of the input node! std::vector& inputs = node_resource.m_socket_accessor->m_outputs; std::vector::iterator iter = inputs.begin(); while (iter != inputs.end()) { Socket& input = *iter; ImGui::PushID(&input); NodeSocketEditor(input); if (ImGui::Button("X")) { RemoveConnectionsForSocket(graph_resource, node_resource, input); iter = inputs.erase(iter); } else { iter++; } ImGui::PopID(); } } } void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) { ImGui::BeginMenuBar(); if (ImGui::Button("Save")) { sGraphGresource.saveToFile("editor_graph.json"); } if (ImGui::Button("Load")) { sGraphGresource.loadFromFile("editor_graph.json"); // for (size_t i = 0, n = sGraphGresource.m_nodes.size(); i < n; i++) { // const AnimNodeResource& node_resource = sGraphGresource.m_nodes[i]; // ImNodes::SetNodeGridSpacePos( // i, // ImVec2(node_resource.m_position[0], node_resource.m_position[1])); // } sGraphLoadedThisFrame = true; } if (ImGui::Button("Clear")) { sGraphGresource.clear(); } char graph_name_buffer[256]; memset(graph_name_buffer, 0, sizeof(graph_name_buffer)); strncpy( graph_name_buffer, sGraphGresource.m_name.c_str(), sizeof(graph_name_buffer)); if (ImGui::InputText("Name", graph_name_buffer, sizeof(graph_name_buffer))) { sGraphGresource.m_name = graph_name_buffer; } ImGui::EndMenuBar(); // // Node editor canvas // ax::NodeEditor::SetCurrentEditor(context); ax::NodeEditor::Begin("Graph Editor"); int node_editor_id = 0; for (size_t node_id = 0, n = sGraphGresource.m_nodes.size(); node_id < n; node_id++) { AnimNodeResource& node_resource = sGraphGresource.m_nodes[node_id]; if (node_id == 0 || node_id == 1) { // continue; } node_editor_id++; if (sGraphLoadedThisFrame) { ax::NodeEditor::SetNodePosition(node_editor_id, ImVec2(node_resource.m_position[0], node_resource.m_position[1])); } ax::NodeEditor::BeginNode(node_editor_id); ImGui::Text("%s", node_resource.m_type_name.c_str()); // // Inputs // std::vector& node_inputs = // node_resource.m_socket_accessor->m_inputs; // for (size_t j = 0, ni = node_inputs.size(); j < ni; j++) { // Socket& socket = node_inputs[j]; // int pin_id = static_cast(node_id) + j * 1000; // ax::NodeEditor::BeginPin(pin_id, ax::NodeEditor::PinKind::Input); // ImGui::Text("%s", socket.m_name.c_str()); // ax::NodeEditor::EndPin(); // } // // // 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]; // int pin_id = static_cast(node_id) + j * 1000 * 1000; // ax::NodeEditor::BeginPin(pin_id, ax::NodeEditor::PinKind::Output); // ImGui::Text("%s", socket.m_name.c_str()); // ax::NodeEditor::EndPin(); // } ax::NodeEditor::EndNode(); } #if 0 int unique_id = 1; ax::NodeEditor::BeginNode(unique_id++); // Node A ImGui::Text("Node A"); ax::NodeEditor::BeginPin(unique_id++, ax::NodeEditor::PinKind::Input); ImGui::Text("In"); ax::NodeEditor::EndPin(); ImGui::SameLine(); ax::NodeEditor::BeginPin(unique_id++, ax::NodeEditor::PinKind::Output); ImGui::Text("Out"); ax::NodeEditor::EndPin(); ax::NodeEditor::EndNode(); // Node B ax::NodeEditor::BeginNode(unique_id++); ImGui::Text("Node B"); ax::NodeEditor::BeginPin(unique_id++, ax::NodeEditor::PinKind::Input); ImGui::Text("In"); ax::NodeEditor::EndPin(); ImGui::SameLine(); ax::NodeEditor::BeginPin(unique_id++, ax::NodeEditor::PinKind::Output); ImGui::Text("Out"); ax::NodeEditor::EndPin(); ax::NodeEditor::EndNode(); #endif // Create Connections if (ax::NodeEditor::BeginCreate()) { ax::NodeEditor::PinId input_pin_id, output_pin_id; if (ax::NodeEditor::QueryNewLink(&input_pin_id, &output_pin_id)) { if (input_pin_id && output_pin_id) { if (ax::NodeEditor::AcceptNewItem()) { } } } } ax::NodeEditor::EndCreate(); ax::NodeEditor::End(); ax::NodeEditor::SetCurrentEditor(nullptr); if (sGraphLoadedThisFrame) { ax::NodeEditor::NavigateToContent(); } sGraphLoadedThisFrame = false; } void LegacyAnimGraphEditorUpdate() { ImGui::BeginMenuBar(); if (ImGui::Button("Save")) { sGraphGresource.saveToFile("editor_graph.json"); } if (ImGui::Button("Load")) { sGraphGresource.loadFromFile("editor_graph.json"); for (size_t i = 0, n = sGraphGresource.m_nodes.size(); i < n; i++) { const AnimNodeResource& node_resource = sGraphGresource.m_nodes[i]; ImNodes::SetNodeGridSpacePos( i, ImVec2(node_resource.m_position[0], node_resource.m_position[1])); } } if (ImGui::Button("Clear")) { sGraphGresource.clear(); } char graph_name_buffer[256]; memset(graph_name_buffer, 0, sizeof(graph_name_buffer)); strncpy( graph_name_buffer, sGraphGresource.m_name.c_str(), sizeof(graph_name_buffer)); if (ImGui::InputText("Name", graph_name_buffer, sizeof(graph_name_buffer))) { sGraphGresource.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 (ImGui::MenuItem("LockTranslationNode")) { node_type_name = "LockTranslationNode"; } if (ImGui::MenuItem("MathAddNode")) { node_type_name = "MathAddNode"; } if (ImGui::MenuItem("MathFloatToVec3Node")) { node_type_name = "MathFloatToVec3Node"; } if (ImGui::MenuItem("ConstScalarNode")) { node_type_name = "ConstScalarNode"; } if (node_type_name != "") { AnimNodeResource node_resource = AnimNodeResourceFactory(node_type_name); size_t node_id = sGraphGresource.m_nodes.size(); ImNodes::SetNodeScreenSpacePos(node_id, ImGui::GetMousePos()); sGraphGresource.m_nodes.push_back(node_resource); } ImGui::EndPopup(); } ImGui::PopStyleVar(ImGuiStyleVar_WindowPadding); } for (size_t i = 0, n = sGraphGresource.m_nodes.size(); i < n; i++) { AnimNodeResource& node_resource = sGraphGresource.m_nodes[i]; ImNodes::BeginNode(i); ImGui::PushItemWidth(110.0f); // Header ImNodes::BeginNodeTitleBar(); if (&node_resource == &sGraphGresource.getGraphOutputNode()) { ImGui::TextUnformatted("Graph Outputs"); } else if (&node_resource == &sGraphGresource.getGraphInputNode()) { ImGui::TextUnformatted("Graph Inputs"); } else { ImGui::TextUnformatted(node_resource.m_type_name.c_str()); } ImNodes::EndNodeTitleBar(); // Inputs std::vector& node_inputs = node_resource.m_socket_accessor->m_inputs; for (size_t j = 0, ni = node_inputs.size(); j < ni; j++) { 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::TextUnformatted(socket.m_name.c_str()); bool socket_connected = sGraphGresource.isSocketConnected(node_resource, socket.m_name); if (!socket_connected && (socket.m_type == SocketType::SocketTypeFloat)) { ImGui::SameLine(); float socket_value = socket.m_value.float_value; ImGui::PushItemWidth( 130.0f - ImGui::CalcTextSize(socket.m_name.c_str()).x); if (ImGui::DragFloat("##hidelabel", &socket_value, 0.01f)) { socket.SetValue(socket_value); } ImGui::PopItemWidth(); } if (!socket_connected && (socket.m_type == SocketType::SocketTypeInt)) { ImGui::SameLine(); int socket_value = socket.m_value.int_value; ImGui::PushItemWidth( 130.0f - ImGui::CalcTextSize(socket.m_name.c_str()).x); if (ImGui::InputInt("##hidelabel", &socket_value, 1)) { socket.SetValue(socket_value); } ImGui::PopItemWidth(); } 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::TextUnformatted(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 = sGraphGresource.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.c_str(), nullptr); } } else if (i == 1) { if (ImGui::Button("+Input")) { AnimNodeResource& graph_input_node = sGraphGresource.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.c_str(), 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]; ImGui::PopItemWidth(); ImNodes::EndNode(); // Ensure flags such as SocketFlagAffectsTime are properly set. node_resource.m_socket_accessor->UpdateFlags(); } for (size_t i = 0, n = sGraphGresource.m_connections.size(); i < n; i++) { const AnimGraphConnectionResource& connection = sGraphGresource.m_connections[i]; int start_attr, end_attr; const AnimNodeResource& source_node = sGraphGresource.m_nodes[connection.source_node_index]; int source_socket_index = source_node.m_socket_accessor->GetOutputIndex( connection.source_socket_name.c_str()); const AnimNodeResource& target_node = sGraphGresource.m_nodes[connection.target_node_index]; int target_socket_index = target_node.m_socket_accessor->GetInputIndex( connection.target_socket_name.c_str()); start_attr = GenerateOutputAttributeId( connection.source_node_index, source_socket_index); end_attr = GenerateInputAttributeId( connection.target_node_index, 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); AnimGraphConnectionResource connection; connection.source_node_index = node_start_id; const AnimNodeResource& source_node = sGraphGresource.m_nodes[node_start_id]; connection.source_socket_name = source_node.m_socket_accessor->m_outputs[node_start_output_index] .m_name; connection.target_node_index = node_end_id; const AnimNodeResource& target_node = sGraphGresource.m_nodes[node_end_id]; connection.target_socket_name = target_node.m_socket_accessor->m_inputs[node_end_input_index].m_name; sGraphGresource.m_connections.push_back(connection); } if (ImGui::IsKeyPressed(ImGuiKey_Delete, false)) { std::cerr << "Delete key!" << std::endl; } // Handle link detachements. int link_id = 0; if (ImNodes::IsLinkDestroyed(&link_id)) { sGraphGresource.m_connections.erase( sGraphGresource.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] < sGraphGresource.m_nodes.size()) { AnimNodeResource& selected_node = sGraphGresource.m_nodes[selected_nodes[0]]; AnimGraphEditorRenderSidebar(sGraphGresource, selected_node); } } ImGui::Columns(1); } void AnimGraphEditorGetRuntimeGraph(AnimGraph& anim_graph) { sGraphGresource.createInstance(anim_graph); }