AnimTestbed/src/AnimGraph/AnimGraphEditor.cc
2024-03-16 19:49:41 +01:00

687 lines
21 KiB
C++

//
// Created by martin on 11.02.22.
//
#include "AnimGraphEditor.h"
#include <sstream>
#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<int>(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<SocketType>(mode_current);
}
}
void RemoveConnectionsForSocket(
AnimGraphResource& graph_resource,
AnimNodeResource& node_resource,
Socket& socket) {
std::vector<AnimGraphConnectionResource>::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<int*>(&property.m_value.int_value),
1);
} else if (property.m_type == SocketType::SocketTypeFloat) {
ImGui::SliderFloat(
property.m_name.c_str(),
reinterpret_cast<float*>(&property.m_value.float_value),
-100.f,
100.f);
} else if (property.m_type == SocketType::SocketTypeBool) {
bool flag_value = property.GetValue<bool>();
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<size_t>(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<Socket>& outputs = node_resource.m_socket_accessor->m_inputs;
std::vector<Socket>::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<Socket>& inputs = node_resource.m_socket_accessor->m_outputs;
std::vector<Socket>::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<Socket>& 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<int>(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<Socket>& 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<int>(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<Socket>& 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<Socket>& 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<float>(
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<float>(
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);
}