Using bits of the blueprints examples for the AnimGraphEditor.
This commit is contained in:
parent
a467715ce3
commit
5a6ac92a48
@ -80,8 +80,14 @@ target_sources(AnimTestbed PRIVATE
|
|||||||
src/main.cc
|
src/main.cc
|
||||||
src/embedded_fonts.h
|
src/embedded_fonts.h
|
||||||
src/SkinnedMeshRenderer.cc
|
src/SkinnedMeshRenderer.cc
|
||||||
src/AnimGraph/AnimGraphEditor.cc
|
src/AnimGraphEditor/AnimGraphEditor.cc
|
||||||
src/AnimGraph/AnimGraphEditor.h
|
src/AnimGraphEditor/AnimGraphEditor.h
|
||||||
|
src/AnimGraphEditor/utilities/builders.cpp
|
||||||
|
src/AnimGraphEditor/utilities/builders.h
|
||||||
|
src/AnimGraphEditor/utilities/drawing.cpp
|
||||||
|
src/AnimGraphEditor/utilities/drawing.h
|
||||||
|
src/AnimGraphEditor/utilities/widgets.cpp
|
||||||
|
src/AnimGraphEditor/utilities/widgets.h
|
||||||
src/Camera.c
|
src/Camera.c
|
||||||
src/SkinnedMesh.cc
|
src/SkinnedMesh.cc
|
||||||
src/SkinnedMesh.h
|
src/SkinnedMesh.h
|
||||||
@ -109,6 +115,10 @@ target_sources(AnimTestbed PRIVATE
|
|||||||
3rdparty/imgui-node-editor/imgui_extra_math.inl
|
3rdparty/imgui-node-editor/imgui_extra_math.inl
|
||||||
3rdparty/imgui-node-editor/crude_json.cpp
|
3rdparty/imgui-node-editor/crude_json.cpp
|
||||||
3rdparty/imgui-node-editor/crude_json.h
|
3rdparty/imgui-node-editor/crude_json.h
|
||||||
|
# ImGui Extension needed for blueprint nodes
|
||||||
|
3rdparty/imgui/imgui_stacklayout.cpp
|
||||||
|
3rdparty/imgui/imgui_stacklayout.h
|
||||||
|
3rdparty/imgui/imgui_stacklayout_internal.h
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(AnimTestbed AnimTestbedCode glfw ozz_base ozz_geometry ozz_animation ${OPENGL_LIBRARIES})
|
target_link_libraries(AnimTestbed AnimTestbedCode glfw ozz_base ozz_geometry ozz_animation ${OPENGL_LIBRARIES})
|
||||||
|
0
media/fonts/Roboto-Medium.ttf
Normal file
0
media/fonts/Roboto-Medium.ttf
Normal file
@ -310,9 +310,6 @@ static bool sAnimGraphResourceBlendTreeFromJson(
|
|||||||
result_graph_resource->m_name = json_data["name"];
|
result_graph_resource->m_name = json_data["name"];
|
||||||
result_graph_resource->m_position[0] = json_data["position"][0];
|
result_graph_resource->m_position[0] = json_data["position"][0];
|
||||||
result_graph_resource->m_position[1] = json_data["position"][1];
|
result_graph_resource->m_position[1] = json_data["position"][1];
|
||||||
assert(result_graph_resource->m_virtual_socket_accessor == nullptr);
|
|
||||||
result_graph_resource->m_virtual_socket_accessor =
|
|
||||||
AnimNodeDescriptorFactory("BlendTree", nullptr);
|
|
||||||
|
|
||||||
// Load nodes
|
// Load nodes
|
||||||
for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) {
|
for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) {
|
||||||
|
@ -56,8 +56,6 @@ struct BlendTreeResource {
|
|||||||
|
|
||||||
void CleanupNodes() {
|
void CleanupNodes() {
|
||||||
for (AnimNodeResource* node_resource : m_nodes) {
|
for (AnimNodeResource* node_resource : m_nodes) {
|
||||||
delete node_resource->m_virtual_socket_accessor;
|
|
||||||
node_resource->m_virtual_socket_accessor = nullptr;
|
|
||||||
delete node_resource;
|
delete node_resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,11 +7,14 @@
|
|||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
#include "3rdparty/imgui-node-editor/imgui_node_editor.h"
|
#include "3rdparty/imgui-node-editor/imgui_node_editor.h"
|
||||||
#include "AnimGraphResource.h"
|
#include "AnimGraphEditor/utilities/builders.h"
|
||||||
|
#include "AnimGraphEditor/utilities/drawing.h"
|
||||||
|
#include "AnimGraphEditor/utilities/widgets.h"
|
||||||
#include "SkinnedMesh.h"
|
#include "SkinnedMesh.h"
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
#include "imnodes.h"
|
#include "imnodes.h"
|
||||||
#include "misc/cpp/imgui_stdlib.h"
|
#include "misc/cpp/imgui_stdlib.h"
|
||||||
|
#include "src/AnimGraph/AnimGraphResource.h"
|
||||||
|
|
||||||
struct EditorState {
|
struct EditorState {
|
||||||
AnimGraphResource* rootGraphResource = nullptr;
|
AnimGraphResource* rootGraphResource = nullptr;
|
||||||
@ -25,6 +28,8 @@ struct EditorState {
|
|||||||
|
|
||||||
static EditorState sEditorState;
|
static EditorState sEditorState;
|
||||||
|
|
||||||
|
constexpr int cPinIconSize = 24;
|
||||||
|
|
||||||
ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) {
|
ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) {
|
||||||
switch (socket_type) {
|
switch (socket_type) {
|
||||||
case SocketType::SocketTypeAnimation:
|
case SocketType::SocketTypeAnimation:
|
||||||
@ -46,12 +51,47 @@ ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) {
|
|||||||
return ImNodesPinShape_Quad;
|
return ImNodesPinShape_Quad;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DrawSocketIcon(const SocketType& socket_type, bool is_connected = false) {
|
||||||
|
ax::Drawing::IconType iconType;
|
||||||
|
ImColor color = ImColor(255, 255, 255);
|
||||||
|
color.Value.w = 1;
|
||||||
|
switch (socket_type) {
|
||||||
|
case SocketType::SocketTypeAnimation:
|
||||||
|
iconType = ax::Drawing::IconType::Flow;
|
||||||
|
break;
|
||||||
|
case SocketType::SocketTypeInt:
|
||||||
|
iconType = ax::Drawing::IconType::Circle;
|
||||||
|
break;
|
||||||
|
case SocketType::SocketTypeFloat:
|
||||||
|
iconType = ax::Drawing::IconType::Circle;
|
||||||
|
break;
|
||||||
|
case SocketType::SocketTypeVec3:
|
||||||
|
iconType = ax::Drawing::IconType::Circle;
|
||||||
|
break;
|
||||||
|
case SocketType::SocketTypeQuat:
|
||||||
|
iconType = ax::Drawing::IconType::Circle;
|
||||||
|
break;
|
||||||
|
case SocketType::SocketTypeBool:
|
||||||
|
iconType = ax::Drawing::IconType::Circle;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ax::Widgets::Icon(
|
||||||
|
ImVec2(
|
||||||
|
static_cast<float>(cPinIconSize),
|
||||||
|
static_cast<float>(cPinIconSize)),
|
||||||
|
iconType,
|
||||||
|
is_connected,
|
||||||
|
color,
|
||||||
|
ImColor(32, 32, 32, 1));
|
||||||
|
}
|
||||||
|
|
||||||
bool NodeSocketEditor(Socket& socket) {
|
bool NodeSocketEditor(Socket& socket) {
|
||||||
bool modified = false;
|
bool modified = false;
|
||||||
int mode_current = static_cast<int>(socket.m_type);
|
int mode_current = static_cast<int>(socket.m_type);
|
||||||
if (ImGui::InputText("Name", &socket.m_name)) {
|
ImGui::Columns(3);
|
||||||
modified = true;
|
|
||||||
}
|
|
||||||
if (ImGui::Combo(
|
if (ImGui::Combo(
|
||||||
"Type",
|
"Type",
|
||||||
&mode_current,
|
&mode_current,
|
||||||
@ -62,6 +102,13 @@ bool NodeSocketEditor(Socket& socket) {
|
|||||||
modified = true;
|
modified = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui::Columns(2);
|
||||||
|
if (ImGui::InputText("Name", &socket.m_name)) {
|
||||||
|
modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Columns(1);
|
||||||
|
|
||||||
return modified;
|
return modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,26 +281,39 @@ void AnimGraphEditorRenderSidebar(
|
|||||||
node_resource->m_virtual_socket_accessor->m_inputs;
|
node_resource->m_virtual_socket_accessor->m_inputs;
|
||||||
|
|
||||||
std::vector<Socket>::iterator iter = outputs.begin();
|
std::vector<Socket>::iterator iter = outputs.begin();
|
||||||
while (iter != outputs.end()) {
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2));
|
||||||
Socket& output = *iter;
|
if (ImGui::BeginTable(
|
||||||
ImGui::PushID(&output);
|
"split",
|
||||||
if (NodeSocketEditor(output)) {
|
2,
|
||||||
AnimGraphResource* current_graph_resource =
|
ImGuiTableFlags_BordersOuter | ImGuiTableFlags_Resizable)) {
|
||||||
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex];
|
while (iter != outputs.end()) {
|
||||||
current_graph_resource->m_socket_accessor->m_inputs = outputs;
|
Socket& socket = *iter;
|
||||||
}
|
ImGui::PushID(&socket);
|
||||||
|
|
||||||
if (ImGui::Button("X")) {
|
ImGui::TableNextRow();
|
||||||
RemoveBlendTreeConnectionsForSocket(
|
ImGui::TableSetColumnIndex(0);
|
||||||
blend_tree_resource,
|
ImGui::AlignTextToFramePadding();
|
||||||
node_resource,
|
bool node_open = ImGui::TreeNode(socket.m_name.c_str());
|
||||||
output);
|
ImGui::TableSetColumnIndex(1);
|
||||||
iter = outputs.erase(iter);
|
|
||||||
} else {
|
if (node_open) {
|
||||||
|
int mode_current = static_cast<int>(socket.m_type);
|
||||||
|
if (ImGui::Combo(
|
||||||
|
"Type",
|
||||||
|
&mode_current,
|
||||||
|
SocketTypeNames,
|
||||||
|
sizeof(SocketTypeNames) / sizeof(char*))) {
|
||||||
|
socket.m_type = static_cast<SocketType>(mode_current);
|
||||||
|
}
|
||||||
|
ImGui::TreePop();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PopID();
|
||||||
iter++;
|
iter++;
|
||||||
}
|
}
|
||||||
ImGui::PopID();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
|
||||||
if (ImGui::Button("+")) {
|
if (ImGui::Button("+")) {
|
||||||
AnimGraphResource* current_graph_resource =
|
AnimGraphResource* current_graph_resource =
|
||||||
@ -306,9 +366,8 @@ void AnimGraphEditorClear() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (sEditorState.rootGraphResource) {
|
if (sEditorState.rootGraphResource) {
|
||||||
delete sEditorState.rootGraphResource->m_virtual_socket_accessor;
|
delete sEditorState.rootGraphResource;
|
||||||
}
|
}
|
||||||
delete sEditorState.rootGraphResource;
|
|
||||||
|
|
||||||
sEditorState.rootGraphResource = new AnimGraphResource();
|
sEditorState.rootGraphResource = new AnimGraphResource();
|
||||||
sEditorState.rootGraphResource->m_name = "Root";
|
sEditorState.rootGraphResource->m_name = "Root";
|
||||||
@ -324,12 +383,68 @@ void AnimGraphEditorClear() {
|
|||||||
sEditorState.hierarchyStackIndex = 0;
|
sEditorState.hierarchyStackIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) {
|
void BlendTreeEditorNodePopup() {
|
||||||
ax::NodeEditor::SetCurrentEditor(context);
|
const bool open_popup = ImGui::IsMouseReleased(ImGuiMouseButton_Right);
|
||||||
|
|
||||||
//
|
if (open_popup && ImGui::IsWindowHovered()) {
|
||||||
// Menu bar
|
ax::NodeEditor::Suspend();
|
||||||
//
|
ImGui::OpenPopup("add node");
|
||||||
|
ax::NodeEditor::Resume();
|
||||||
|
sEditorState.mousePopupStart = ImGui::GetMousePos();
|
||||||
|
}
|
||||||
|
|
||||||
|
ax::NodeEditor::Suspend();
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.f, 8.f));
|
||||||
|
if (ImGui::BeginPopup("add node")) {
|
||||||
|
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 (ImGui::MenuItem("BlendTree")) {
|
||||||
|
node_type_name = "BlendTree";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node_type_name.empty()) {
|
||||||
|
AnimNodeResource* node_resource = AnimNodeResourceFactory(node_type_name);
|
||||||
|
ax::NodeEditor::SetNodePosition(
|
||||||
|
ax::NodeEditor::NodeId(node_resource),
|
||||||
|
sEditorState.mousePopupStart);
|
||||||
|
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex]
|
||||||
|
->m_blend_tree_resource.AddNode(node_resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
ax::NodeEditor::Resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimGraphEditorMenuBar() {
|
||||||
ImGui::BeginMenuBar();
|
ImGui::BeginMenuBar();
|
||||||
if (ImGui::Button("Save")) {
|
if (ImGui::Button("Save")) {
|
||||||
sEditorState.rootGraphResource->SaveToFile("editor_graph.json");
|
sEditorState.rootGraphResource->SaveToFile("editor_graph.json");
|
||||||
@ -358,10 +473,9 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImGui::EndMenuBar();
|
ImGui::EndMenuBar();
|
||||||
|
}
|
||||||
|
|
||||||
//
|
void AnimGraphEditorBreadcrumbNavigation() {
|
||||||
// Breadcrumb navigation
|
|
||||||
//
|
|
||||||
for (size_t i = 0, n = sEditorState.hierarchyStack.size(); i < n; i++) {
|
for (size_t i = 0, n = sEditorState.hierarchyStack.size(); i < n; i++) {
|
||||||
AnimGraphResource* graph_resource =
|
AnimGraphResource* graph_resource =
|
||||||
dynamic_cast<AnimGraphResource*>(sEditorState.hierarchyStack[i]);
|
dynamic_cast<AnimGraphResource*>(sEditorState.hierarchyStack[i]);
|
||||||
@ -389,100 +503,10 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) {
|
|||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Columns(2);
|
void HandleConnectionCreation(
|
||||||
|
BlendTreeResource& current_blend_tree) { // Create Connections
|
||||||
//
|
|
||||||
// Node editor canvas
|
|
||||||
//
|
|
||||||
ax::NodeEditor::Begin("Graph Editor");
|
|
||||||
|
|
||||||
AnimGraphResource* current_graph =
|
|
||||||
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex];
|
|
||||||
BlendTreeResource& current_blend_tree = current_graph->m_blend_tree_resource;
|
|
||||||
|
|
||||||
for (size_t node_index = 0, n = current_blend_tree.GetNumNodes();
|
|
||||||
node_index < n;
|
|
||||||
node_index++) {
|
|
||||||
AnimNodeResource* node_resource = current_blend_tree.GetNode(node_index);
|
|
||||||
ax::NodeEditor::NodeId node_id(node_resource);
|
|
||||||
|
|
||||||
if (sEditorState.isGraphLoadedThisFrame) {
|
|
||||||
ax::NodeEditor::SetNodePosition(
|
|
||||||
node_id,
|
|
||||||
ImVec2(node_resource->m_position[0], node_resource->m_position[1]));
|
|
||||||
}
|
|
||||||
ax::NodeEditor::BeginNode(node_id);
|
|
||||||
ImGui::Text("%s", node_resource->m_node_type_name.c_str());
|
|
||||||
|
|
||||||
// Inputs
|
|
||||||
std::vector<Socket> node_inputs =
|
|
||||||
current_blend_tree.GetNodeInputSockets(node_resource);
|
|
||||||
for (size_t j = 0, ni = node_inputs.size(); j < ni; j++) {
|
|
||||||
Socket& socket = node_inputs[j];
|
|
||||||
ax::NodeEditor::BeginPin(
|
|
||||||
NodeIndexAndSocketIndexToInputPinId(
|
|
||||||
static_cast<int>(node_index),
|
|
||||||
static_cast<int>(j)),
|
|
||||||
ax::NodeEditor::PinKind::Input);
|
|
||||||
ImGui::Text("%s", socket.m_name.c_str());
|
|
||||||
ax::NodeEditor::EndPin();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Outputs
|
|
||||||
std::vector<Socket> node_outputs =
|
|
||||||
current_blend_tree.GetNodeOutputSockets(node_resource);
|
|
||||||
for (size_t j = 0, ni = node_outputs.size(); j < ni; j++) {
|
|
||||||
Socket& socket = node_outputs[j];
|
|
||||||
ax::NodeEditor::BeginPin(
|
|
||||||
NodeIndexAndSocketIndexToOutputPinId(
|
|
||||||
static_cast<int>(node_index),
|
|
||||||
static_cast<int>(j)),
|
|
||||||
ax::NodeEditor::PinKind::Output);
|
|
||||||
ImGui::Text("%s", socket.m_name.c_str());
|
|
||||||
ax::NodeEditor::EndPin();
|
|
||||||
}
|
|
||||||
|
|
||||||
ax::NodeEditor::EndNode();
|
|
||||||
|
|
||||||
ImVec2 node_position = ax::NodeEditor::GetNodePosition(node_id);
|
|
||||||
node_resource->m_position[0] = node_position.x;
|
|
||||||
node_resource->m_position[1] = node_position.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
int link_id = 0;
|
|
||||||
for (size_t connection_id = 0, n = current_blend_tree.GetNumConnections();
|
|
||||||
connection_id < n;
|
|
||||||
connection_id++) {
|
|
||||||
const BlendTreeConnectionResource* connection_resource =
|
|
||||||
current_blend_tree.GetConnection(connection_id);
|
|
||||||
|
|
||||||
const AnimNodeResource* source_node_resource =
|
|
||||||
current_blend_tree.GetNode(connection_resource->source_node_index);
|
|
||||||
int source_socket_index =
|
|
||||||
source_node_resource->m_virtual_socket_accessor->GetOutputIndex(
|
|
||||||
connection_resource->source_socket_name.c_str());
|
|
||||||
|
|
||||||
const AnimNodeResource* target_node_resource =
|
|
||||||
current_blend_tree.GetNode(connection_resource->target_node_index);
|
|
||||||
int target_socket_index =
|
|
||||||
target_node_resource->m_virtual_socket_accessor->GetInputIndex(
|
|
||||||
connection_resource->target_socket_name.c_str());
|
|
||||||
|
|
||||||
int source_socket_pin_id = NodeIndexAndSocketIndexToOutputPinId(
|
|
||||||
static_cast<int>(connection_resource->source_node_index),
|
|
||||||
source_socket_index);
|
|
||||||
int target_socket_pin_id = NodeIndexAndSocketIndexToInputPinId(
|
|
||||||
static_cast<int>(connection_resource->target_node_index),
|
|
||||||
target_socket_index);
|
|
||||||
|
|
||||||
ax::NodeEditor::Link(
|
|
||||||
ax::NodeEditor::LinkId(connection_resource),
|
|
||||||
source_socket_pin_id,
|
|
||||||
target_socket_pin_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Connections
|
|
||||||
if (ax::NodeEditor::BeginCreate()) {
|
if (ax::NodeEditor::BeginCreate()) {
|
||||||
ax::NodeEditor::PinId input_pin_id, output_pin_id;
|
ax::NodeEditor::PinId input_pin_id, output_pin_id;
|
||||||
if (ax::NodeEditor::QueryNewLink(&input_pin_id, &output_pin_id)) {
|
if (ax::NodeEditor::QueryNewLink(&input_pin_id, &output_pin_id)) {
|
||||||
@ -549,69 +573,137 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ax::NodeEditor::EndCreate();
|
ax::NodeEditor::EndCreate();
|
||||||
|
}
|
||||||
|
|
||||||
// Popup menu
|
void BlendTreeRenderNodes(
|
||||||
{
|
BlendTreeResource& current_blend_tree,
|
||||||
const bool open_popup = ImGui::IsMouseReleased(ImGuiMouseButton_Right);
|
ax::NodeEditor::Utilities::BlueprintNodeBuilder& builder) {
|
||||||
|
for (size_t node_index = 0, n = current_blend_tree.GetNumNodes();
|
||||||
|
node_index < n;
|
||||||
|
node_index++) {
|
||||||
|
AnimNodeResource* node_resource = current_blend_tree.GetNode(node_index);
|
||||||
|
|
||||||
if (open_popup && ImGui::IsWindowHovered()) {
|
ax::NodeEditor::NodeId node_id(node_resource);
|
||||||
ax::NodeEditor::Suspend();
|
|
||||||
ImGui::OpenPopup("add node");
|
builder.Begin(node_id);
|
||||||
ax::NodeEditor::Resume();
|
|
||||||
sEditorState.mousePopupStart = ImGui::GetMousePos();
|
if (sEditorState.isGraphLoadedThisFrame) {
|
||||||
|
ax::NodeEditor::SetNodePosition(
|
||||||
|
node_id,
|
||||||
|
ImVec2(node_resource->m_position[0], node_resource->m_position[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
ax::NodeEditor::Suspend();
|
builder.Header();
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.f, 8.f));
|
ImGui::Text("%s", node_resource->m_node_type_name.c_str());
|
||||||
if (ImGui::BeginPopup("add node")) {
|
ImGui::Spring(0);
|
||||||
std::string node_type_name = "";
|
builder.EndHeader();
|
||||||
if (ImGui::MenuItem("AnimSampler")) {
|
|
||||||
node_type_name = "AnimSampler";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui::MenuItem("Blend2")) {
|
// Inputs
|
||||||
node_type_name = "Blend2";
|
std::vector<Socket> node_inputs =
|
||||||
}
|
current_blend_tree.GetNodeInputSockets(node_resource);
|
||||||
|
for (size_t j = 0, ni = node_inputs.size(); j < ni; j++) {
|
||||||
|
Socket& socket = node_inputs[j];
|
||||||
|
builder.Input(NodeIndexAndSocketIndexToInputPinId(
|
||||||
|
static_cast<int>(node_index),
|
||||||
|
static_cast<int>(j)));
|
||||||
|
|
||||||
if (ImGui::MenuItem("SpeedScale")) {
|
DrawSocketIcon(
|
||||||
node_type_name = "SpeedScale";
|
socket.m_type,
|
||||||
}
|
current_blend_tree.IsSocketConnected(node_resource, socket.m_name));
|
||||||
|
ImGui::Spring(0);
|
||||||
|
|
||||||
if (ImGui::MenuItem("LockTranslationNode")) {
|
//ImGui::PushItemWidth(100.0f);
|
||||||
node_type_name = "LockTranslationNode";
|
ImGui::Text("%s", socket.m_name.c_str());
|
||||||
}
|
//ImGui::PopItemWidth();
|
||||||
|
builder.EndInput();
|
||||||
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 (ImGui::MenuItem("BlendTree")) {
|
|
||||||
node_type_name = "BlendTree";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!node_type_name.empty()) {
|
|
||||||
AnimNodeResource* node_resource =
|
|
||||||
AnimNodeResourceFactory(node_type_name);
|
|
||||||
ax::NodeEditor::SetNodePosition(
|
|
||||||
ax::NodeEditor::NodeId(node_resource),
|
|
||||||
sEditorState.mousePopupStart);
|
|
||||||
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex]
|
|
||||||
->m_blend_tree_resource.AddNode(node_resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::EndPopup();
|
|
||||||
}
|
}
|
||||||
ImGui::PopStyleVar();
|
|
||||||
ax::NodeEditor::Resume();
|
// Outputs
|
||||||
|
std::vector<Socket> node_outputs =
|
||||||
|
current_blend_tree.GetNodeOutputSockets(node_resource);
|
||||||
|
for (size_t j = 0, ni = node_outputs.size(); j < ni; j++) {
|
||||||
|
Socket& socket = node_outputs[j];
|
||||||
|
builder.Output(NodeIndexAndSocketIndexToOutputPinId(
|
||||||
|
static_cast<int>(node_index),
|
||||||
|
static_cast<int>(j)));
|
||||||
|
|
||||||
|
ImGui::Text("%s", socket.m_name.c_str());
|
||||||
|
|
||||||
|
ImGui::Spring(0);
|
||||||
|
DrawSocketIcon(
|
||||||
|
socket.m_type,
|
||||||
|
current_blend_tree.IsSocketConnected(node_resource, socket.m_name));
|
||||||
|
|
||||||
|
builder.EndOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImVec2 node_position = ax::NodeEditor::GetNodePosition(node_id);
|
||||||
|
node_resource->m_position[0] = node_position.x;
|
||||||
|
node_resource->m_position[1] = node_position.y;
|
||||||
|
|
||||||
|
builder.End();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BlendTreeRenderConnections(BlendTreeResource& current_blend_tree) {
|
||||||
|
for (size_t connection_id = 0, n = current_blend_tree.GetNumConnections();
|
||||||
|
connection_id < n;
|
||||||
|
connection_id++) {
|
||||||
|
const BlendTreeConnectionResource* connection_resource =
|
||||||
|
current_blend_tree.GetConnection(connection_id);
|
||||||
|
|
||||||
|
const AnimNodeResource* source_node_resource =
|
||||||
|
current_blend_tree.GetNode(connection_resource->source_node_index);
|
||||||
|
int source_socket_index =
|
||||||
|
source_node_resource->m_virtual_socket_accessor->GetOutputIndex(
|
||||||
|
connection_resource->source_socket_name.c_str());
|
||||||
|
|
||||||
|
const AnimNodeResource* target_node_resource =
|
||||||
|
current_blend_tree.GetNode(connection_resource->target_node_index);
|
||||||
|
int target_socket_index =
|
||||||
|
target_node_resource->m_virtual_socket_accessor->GetInputIndex(
|
||||||
|
connection_resource->target_socket_name.c_str());
|
||||||
|
|
||||||
|
int source_socket_pin_id = NodeIndexAndSocketIndexToOutputPinId(
|
||||||
|
static_cast<int>(connection_resource->source_node_index),
|
||||||
|
source_socket_index);
|
||||||
|
int target_socket_pin_id = NodeIndexAndSocketIndexToInputPinId(
|
||||||
|
static_cast<int>(connection_resource->target_node_index),
|
||||||
|
target_socket_index);
|
||||||
|
|
||||||
|
ax::NodeEditor::Link(
|
||||||
|
ax::NodeEditor::LinkId(connection_resource),
|
||||||
|
source_socket_pin_id,
|
||||||
|
target_socket_pin_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) {
|
||||||
|
ax::NodeEditor::SetCurrentEditor(context);
|
||||||
|
|
||||||
|
AnimGraphEditorMenuBar();
|
||||||
|
|
||||||
|
AnimGraphEditorBreadcrumbNavigation();
|
||||||
|
|
||||||
|
ImGui::Columns(2);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Node editor canvas
|
||||||
|
//
|
||||||
|
ax::NodeEditor::Begin("Graph Editor");
|
||||||
|
|
||||||
|
AnimGraphResource* current_graph =
|
||||||
|
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex];
|
||||||
|
BlendTreeResource& current_blend_tree = current_graph->m_blend_tree_resource;
|
||||||
|
|
||||||
|
ax::NodeEditor::Utilities::BlueprintNodeBuilder builder;
|
||||||
|
|
||||||
|
BlendTreeRenderNodes(current_blend_tree, builder);
|
||||||
|
|
||||||
|
BlendTreeRenderConnections(current_blend_tree);
|
||||||
|
|
||||||
|
HandleConnectionCreation(current_blend_tree);
|
||||||
|
|
||||||
|
BlendTreeEditorNodePopup();
|
||||||
|
|
||||||
ax::NodeEditor::End();
|
ax::NodeEditor::End();
|
||||||
|
|
310
src/AnimGraphEditor/utilities/builders.cpp
Normal file
310
src/AnimGraphEditor/utilities/builders.cpp
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// LICENSE
|
||||||
|
// This software is dual-licensed to the public domain and under the following
|
||||||
|
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||||
|
// publish, and distribute this file as you see fit.
|
||||||
|
//
|
||||||
|
// CREDITS
|
||||||
|
// Written by Michal Cichon
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
# define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
|
# include "builders.h"
|
||||||
|
# include <imgui_internal.h>
|
||||||
|
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
namespace ed = ax::NodeEditor;
|
||||||
|
namespace util = ax::NodeEditor::Utilities;
|
||||||
|
|
||||||
|
util::BlueprintNodeBuilder::BlueprintNodeBuilder(ImTextureID texture, int textureWidth, int textureHeight):
|
||||||
|
HeaderTextureId(texture),
|
||||||
|
HeaderTextureWidth(textureWidth),
|
||||||
|
HeaderTextureHeight(textureHeight),
|
||||||
|
CurrentNodeId(0),
|
||||||
|
CurrentStage(Stage::Invalid),
|
||||||
|
HasHeader(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::BlueprintNodeBuilder::Begin(ed::NodeId id)
|
||||||
|
{
|
||||||
|
HasHeader = false;
|
||||||
|
HeaderMin = HeaderMax = ImVec2();
|
||||||
|
|
||||||
|
ed::PushStyleVar(StyleVar_NodePadding, ImVec4(8, 4, 8, 8));
|
||||||
|
|
||||||
|
ed::BeginNode(id);
|
||||||
|
|
||||||
|
ImGui::PushID(id.AsPointer());
|
||||||
|
CurrentNodeId = id;
|
||||||
|
|
||||||
|
SetStage(Stage::Begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::BlueprintNodeBuilder::End()
|
||||||
|
{
|
||||||
|
SetStage(Stage::End);
|
||||||
|
|
||||||
|
ed::EndNode();
|
||||||
|
|
||||||
|
if (ImGui::IsItemVisible())
|
||||||
|
{
|
||||||
|
auto alpha = static_cast<int>(255 * ImGui::GetStyle().Alpha);
|
||||||
|
|
||||||
|
auto drawList = ed::GetNodeBackgroundDrawList(CurrentNodeId);
|
||||||
|
|
||||||
|
const auto halfBorderWidth = ed::GetStyle().NodeBorderWidth * 0.5f;
|
||||||
|
|
||||||
|
auto headerColor = IM_COL32(0, 0, 0, alpha) | (HeaderColor & IM_COL32(255, 255, 255, 0));
|
||||||
|
if ((HeaderMax.x > HeaderMin.x) && (HeaderMax.y > HeaderMin.y) && HeaderTextureId)
|
||||||
|
{
|
||||||
|
const auto uv = ImVec2(
|
||||||
|
(HeaderMax.x - HeaderMin.x) / (float)(4.0f * HeaderTextureWidth),
|
||||||
|
(HeaderMax.y - HeaderMin.y) / (float)(4.0f * HeaderTextureHeight));
|
||||||
|
|
||||||
|
drawList->AddImageRounded(HeaderTextureId,
|
||||||
|
HeaderMin - ImVec2(8 - halfBorderWidth, 4 - halfBorderWidth),
|
||||||
|
HeaderMax + ImVec2(8 - halfBorderWidth, 0),
|
||||||
|
ImVec2(0.0f, 0.0f), uv,
|
||||||
|
#if IMGUI_VERSION_NUM > 18101
|
||||||
|
headerColor, GetStyle().NodeRounding, ImDrawFlags_RoundCornersTop);
|
||||||
|
#else
|
||||||
|
headerColor, GetStyle().NodeRounding, 1 | 2);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (ContentMin.y > HeaderMax.y)
|
||||||
|
{
|
||||||
|
drawList->AddLine(
|
||||||
|
ImVec2(HeaderMin.x - (8 - halfBorderWidth), HeaderMax.y - 0.5f),
|
||||||
|
ImVec2(HeaderMax.x + (8 - halfBorderWidth), HeaderMax.y - 0.5f),
|
||||||
|
ImColor(255, 255, 255, 96 * alpha / (3 * 255)), 1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentNodeId = 0;
|
||||||
|
|
||||||
|
ImGui::PopID();
|
||||||
|
|
||||||
|
ed::PopStyleVar();
|
||||||
|
|
||||||
|
SetStage(Stage::Invalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::BlueprintNodeBuilder::Header(const ImVec4& color)
|
||||||
|
{
|
||||||
|
HeaderColor = ImColor(color);
|
||||||
|
SetStage(Stage::Header);
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::BlueprintNodeBuilder::EndHeader()
|
||||||
|
{
|
||||||
|
SetStage(Stage::Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::BlueprintNodeBuilder::Input(ed::PinId id)
|
||||||
|
{
|
||||||
|
if (CurrentStage == Stage::Begin)
|
||||||
|
SetStage(Stage::Content);
|
||||||
|
|
||||||
|
const auto applyPadding = (CurrentStage == Stage::Input);
|
||||||
|
|
||||||
|
SetStage(Stage::Input);
|
||||||
|
|
||||||
|
if (applyPadding)
|
||||||
|
ImGui::Spring(0);
|
||||||
|
|
||||||
|
Pin(id, PinKind::Input);
|
||||||
|
|
||||||
|
ImGui::BeginHorizontal(id.AsPointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::BlueprintNodeBuilder::EndInput()
|
||||||
|
{
|
||||||
|
ImGui::EndHorizontal();
|
||||||
|
|
||||||
|
EndPin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::BlueprintNodeBuilder::Middle()
|
||||||
|
{
|
||||||
|
if (CurrentStage == Stage::Begin)
|
||||||
|
SetStage(Stage::Content);
|
||||||
|
|
||||||
|
SetStage(Stage::Middle);
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::BlueprintNodeBuilder::Output(ed::PinId id)
|
||||||
|
{
|
||||||
|
if (CurrentStage == Stage::Begin)
|
||||||
|
SetStage(Stage::Content);
|
||||||
|
|
||||||
|
const auto applyPadding = (CurrentStage == Stage::Output);
|
||||||
|
|
||||||
|
SetStage(Stage::Output);
|
||||||
|
|
||||||
|
if (applyPadding)
|
||||||
|
ImGui::Spring(0);
|
||||||
|
|
||||||
|
Pin(id, PinKind::Output);
|
||||||
|
|
||||||
|
ImGui::BeginHorizontal(id.AsPointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::BlueprintNodeBuilder::EndOutput()
|
||||||
|
{
|
||||||
|
ImGui::EndHorizontal();
|
||||||
|
|
||||||
|
EndPin();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool util::BlueprintNodeBuilder::SetStage(Stage stage)
|
||||||
|
{
|
||||||
|
if (stage == CurrentStage)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto oldStage = CurrentStage;
|
||||||
|
CurrentStage = stage;
|
||||||
|
|
||||||
|
ImVec2 cursor;
|
||||||
|
switch (oldStage)
|
||||||
|
{
|
||||||
|
case Stage::Begin:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage::Header:
|
||||||
|
ImGui::EndHorizontal();
|
||||||
|
HeaderMin = ImGui::GetItemRectMin();
|
||||||
|
HeaderMax = ImGui::GetItemRectMax();
|
||||||
|
|
||||||
|
// spacing between header and content
|
||||||
|
ImGui::Spring(0, ImGui::GetStyle().ItemSpacing.y * 2.0f);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage::Content:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage::Input:
|
||||||
|
ed::PopStyleVar(2);
|
||||||
|
|
||||||
|
ImGui::Spring(1, 0);
|
||||||
|
ImGui::EndVertical();
|
||||||
|
|
||||||
|
// #debug
|
||||||
|
// ImGui::GetWindowDrawList()->AddRect(
|
||||||
|
// ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255, 0, 0, 255));
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage::Middle:
|
||||||
|
ImGui::EndVertical();
|
||||||
|
|
||||||
|
// #debug
|
||||||
|
// ImGui::GetWindowDrawList()->AddRect(
|
||||||
|
// ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255, 0, 0, 255));
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage::Output:
|
||||||
|
ed::PopStyleVar(2);
|
||||||
|
|
||||||
|
ImGui::Spring(1, 0);
|
||||||
|
ImGui::EndVertical();
|
||||||
|
|
||||||
|
// #debug
|
||||||
|
// ImGui::GetWindowDrawList()->AddRect(
|
||||||
|
// ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255, 0, 0, 255));
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage::End:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage::Invalid:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (stage)
|
||||||
|
{
|
||||||
|
case Stage::Begin:
|
||||||
|
ImGui::BeginVertical("node");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage::Header:
|
||||||
|
HasHeader = true;
|
||||||
|
|
||||||
|
ImGui::BeginHorizontal("header");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage::Content:
|
||||||
|
if (oldStage == Stage::Begin)
|
||||||
|
ImGui::Spring(0);
|
||||||
|
|
||||||
|
ImGui::BeginHorizontal("content");
|
||||||
|
ImGui::Spring(0, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage::Input:
|
||||||
|
ImGui::BeginVertical("inputs", ImVec2(0, 0), 0.0f);
|
||||||
|
|
||||||
|
ed::PushStyleVar(ed::StyleVar_PivotAlignment, ImVec2(0, 0.5f));
|
||||||
|
ed::PushStyleVar(ed::StyleVar_PivotSize, ImVec2(0, 0));
|
||||||
|
|
||||||
|
if (!HasHeader)
|
||||||
|
ImGui::Spring(1, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage::Middle:
|
||||||
|
ImGui::Spring(1);
|
||||||
|
ImGui::BeginVertical("middle", ImVec2(0, 0), 1.0f);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage::Output:
|
||||||
|
if (oldStage == Stage::Middle || oldStage == Stage::Input)
|
||||||
|
ImGui::Spring(1);
|
||||||
|
else
|
||||||
|
ImGui::Spring(1, 0);
|
||||||
|
ImGui::BeginVertical("outputs", ImVec2(0, 0), 1.0f);
|
||||||
|
|
||||||
|
ed::PushStyleVar(ed::StyleVar_PivotAlignment, ImVec2(1.0f, 0.5f));
|
||||||
|
ed::PushStyleVar(ed::StyleVar_PivotSize, ImVec2(0, 0));
|
||||||
|
|
||||||
|
if (!HasHeader)
|
||||||
|
ImGui::Spring(1, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage::End:
|
||||||
|
if (oldStage == Stage::Input)
|
||||||
|
ImGui::Spring(1, 0);
|
||||||
|
if (oldStage != Stage::Begin)
|
||||||
|
ImGui::EndHorizontal();
|
||||||
|
ContentMin = ImGui::GetItemRectMin();
|
||||||
|
ContentMax = ImGui::GetItemRectMax();
|
||||||
|
|
||||||
|
//ImGui::Spring(0);
|
||||||
|
ImGui::EndVertical();
|
||||||
|
NodeMin = ImGui::GetItemRectMin();
|
||||||
|
NodeMax = ImGui::GetItemRectMax();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage::Invalid:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::BlueprintNodeBuilder::Pin(ed::PinId id, ed::PinKind kind)
|
||||||
|
{
|
||||||
|
ed::BeginPin(id, kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::BlueprintNodeBuilder::EndPin()
|
||||||
|
{
|
||||||
|
ed::EndPin();
|
||||||
|
|
||||||
|
// #debug
|
||||||
|
// ImGui::GetWindowDrawList()->AddRectFilled(
|
||||||
|
// ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), IM_COL32(255, 0, 0, 64));
|
||||||
|
}
|
76
src/AnimGraphEditor/utilities/builders.h
Normal file
76
src/AnimGraphEditor/utilities/builders.h
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// LICENSE
|
||||||
|
// This software is dual-licensed to the public domain and under the following
|
||||||
|
// license: you are granted a perpetual, irrevocable license to copy, modify,
|
||||||
|
// publish, and distribute this file as you see fit.
|
||||||
|
//
|
||||||
|
// CREDITS
|
||||||
|
// Written by Michal Cichon
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
#include "3rdparty/imgui-node-editor/imgui_node_editor.h"
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
namespace ax {
|
||||||
|
namespace NodeEditor {
|
||||||
|
namespace Utilities {
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
struct BlueprintNodeBuilder {
|
||||||
|
BlueprintNodeBuilder(
|
||||||
|
ImTextureID texture = 0,
|
||||||
|
int textureWidth = 0,
|
||||||
|
int textureHeight = 0);
|
||||||
|
|
||||||
|
void Begin(NodeId id);
|
||||||
|
void End();
|
||||||
|
|
||||||
|
void Header(const ImVec4& color = ImVec4(1, 1, 1, 1));
|
||||||
|
void EndHeader();
|
||||||
|
|
||||||
|
void Input(PinId id);
|
||||||
|
void EndInput();
|
||||||
|
|
||||||
|
void Middle();
|
||||||
|
|
||||||
|
void Output(PinId id);
|
||||||
|
void EndOutput();
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class Stage {
|
||||||
|
Invalid,
|
||||||
|
Begin,
|
||||||
|
Header,
|
||||||
|
Content,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
Middle,
|
||||||
|
End
|
||||||
|
};
|
||||||
|
|
||||||
|
bool SetStage(Stage stage);
|
||||||
|
|
||||||
|
void Pin(PinId id, ax::NodeEditor::PinKind kind);
|
||||||
|
void EndPin();
|
||||||
|
|
||||||
|
ImTextureID HeaderTextureId;
|
||||||
|
int HeaderTextureWidth;
|
||||||
|
int HeaderTextureHeight;
|
||||||
|
NodeId CurrentNodeId;
|
||||||
|
Stage CurrentStage;
|
||||||
|
ImU32 HeaderColor;
|
||||||
|
ImVec2 NodeMin;
|
||||||
|
ImVec2 NodeMax;
|
||||||
|
ImVec2 HeaderMin;
|
||||||
|
ImVec2 HeaderMax;
|
||||||
|
ImVec2 ContentMin;
|
||||||
|
ImVec2 ContentMax;
|
||||||
|
bool HasHeader;
|
||||||
|
};
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
} // namespace Utilities
|
||||||
|
} // namespace NodeEditor
|
||||||
|
} // namespace ax
|
252
src/AnimGraphEditor/utilities/drawing.cpp
Normal file
252
src/AnimGraphEditor/utilities/drawing.cpp
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
# define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
|
# include "drawing.h"
|
||||||
|
# include <imgui_internal.h>
|
||||||
|
|
||||||
|
void ax::Drawing::DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor)
|
||||||
|
{
|
||||||
|
auto rect = ImRect(a, b);
|
||||||
|
auto rect_x = rect.Min.x;
|
||||||
|
auto rect_y = rect.Min.y;
|
||||||
|
auto rect_w = rect.Max.x - rect.Min.x;
|
||||||
|
auto rect_h = rect.Max.y - rect.Min.y;
|
||||||
|
auto rect_center_x = (rect.Min.x + rect.Max.x) * 0.5f;
|
||||||
|
auto rect_center_y = (rect.Min.y + rect.Max.y) * 0.5f;
|
||||||
|
auto rect_center = ImVec2(rect_center_x, rect_center_y);
|
||||||
|
const auto outline_scale = rect_w / 24.0f;
|
||||||
|
const auto extra_segments = static_cast<int>(2 * outline_scale); // for full circle
|
||||||
|
|
||||||
|
if (type == IconType::Flow)
|
||||||
|
{
|
||||||
|
const auto origin_scale = rect_w / 24.0f;
|
||||||
|
|
||||||
|
const auto offset_x = 1.0f * origin_scale;
|
||||||
|
const auto offset_y = 0.0f * origin_scale;
|
||||||
|
const auto margin = (filled ? 2.0f : 2.0f) * origin_scale;
|
||||||
|
const auto rounding = 0.1f * origin_scale;
|
||||||
|
const auto tip_round = 0.7f; // percentage of triangle edge (for tip)
|
||||||
|
//const auto edge_round = 0.7f; // percentage of triangle edge (for corner)
|
||||||
|
const auto canvas = ImRect(
|
||||||
|
rect.Min.x + margin + offset_x,
|
||||||
|
rect.Min.y + margin + offset_y,
|
||||||
|
rect.Max.x - margin + offset_x,
|
||||||
|
rect.Max.y - margin + offset_y);
|
||||||
|
const auto canvas_x = canvas.Min.x;
|
||||||
|
const auto canvas_y = canvas.Min.y;
|
||||||
|
const auto canvas_w = canvas.Max.x - canvas.Min.x;
|
||||||
|
const auto canvas_h = canvas.Max.y - canvas.Min.y;
|
||||||
|
|
||||||
|
const auto left = canvas_x + canvas_w * 0.5f * 0.3f;
|
||||||
|
const auto right = canvas_x + canvas_w - canvas_w * 0.5f * 0.3f;
|
||||||
|
const auto top = canvas_y + canvas_h * 0.5f * 0.2f;
|
||||||
|
const auto bottom = canvas_y + canvas_h - canvas_h * 0.5f * 0.2f;
|
||||||
|
const auto center_y = (top + bottom) * 0.5f;
|
||||||
|
//const auto angle = AX_PI * 0.5f * 0.5f * 0.5f;
|
||||||
|
|
||||||
|
const auto tip_top = ImVec2(canvas_x + canvas_w * 0.5f, top);
|
||||||
|
const auto tip_right = ImVec2(right, center_y);
|
||||||
|
const auto tip_bottom = ImVec2(canvas_x + canvas_w * 0.5f, bottom);
|
||||||
|
|
||||||
|
drawList->PathLineTo(ImVec2(left, top) + ImVec2(0, rounding));
|
||||||
|
drawList->PathBezierCubicCurveTo(
|
||||||
|
ImVec2(left, top),
|
||||||
|
ImVec2(left, top),
|
||||||
|
ImVec2(left, top) + ImVec2(rounding, 0));
|
||||||
|
drawList->PathLineTo(tip_top);
|
||||||
|
drawList->PathLineTo(tip_top + (tip_right - tip_top) * tip_round);
|
||||||
|
drawList->PathBezierCubicCurveTo(
|
||||||
|
tip_right,
|
||||||
|
tip_right,
|
||||||
|
tip_bottom + (tip_right - tip_bottom) * tip_round);
|
||||||
|
drawList->PathLineTo(tip_bottom);
|
||||||
|
drawList->PathLineTo(ImVec2(left, bottom) + ImVec2(rounding, 0));
|
||||||
|
drawList->PathBezierCubicCurveTo(
|
||||||
|
ImVec2(left, bottom),
|
||||||
|
ImVec2(left, bottom),
|
||||||
|
ImVec2(left, bottom) - ImVec2(0, rounding));
|
||||||
|
|
||||||
|
if (!filled)
|
||||||
|
{
|
||||||
|
if (innerColor & 0xFF000000)
|
||||||
|
drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor);
|
||||||
|
|
||||||
|
drawList->PathStroke(color, true, 2.0f * outline_scale);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
drawList->PathFillConvex(color);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto triangleStart = rect_center_x + 0.32f * rect_w;
|
||||||
|
|
||||||
|
auto rect_offset = -static_cast<int>(rect_w * 0.25f * 0.25f);
|
||||||
|
|
||||||
|
rect.Min.x += rect_offset;
|
||||||
|
rect.Max.x += rect_offset;
|
||||||
|
rect_x += rect_offset;
|
||||||
|
rect_center_x += rect_offset * 0.5f;
|
||||||
|
rect_center.x += rect_offset * 0.5f;
|
||||||
|
|
||||||
|
if (type == IconType::Circle)
|
||||||
|
{
|
||||||
|
const auto c = rect_center;
|
||||||
|
|
||||||
|
if (!filled)
|
||||||
|
{
|
||||||
|
const auto r = 0.5f * rect_w / 2.0f - 0.5f;
|
||||||
|
|
||||||
|
if (innerColor & 0xFF000000)
|
||||||
|
drawList->AddCircleFilled(c, r, innerColor, 12 + extra_segments);
|
||||||
|
drawList->AddCircle(c, r, color, 12 + extra_segments, 2.0f * outline_scale);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
drawList->AddCircleFilled(c, 0.5f * rect_w / 2.0f, color, 12 + extra_segments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == IconType::Square)
|
||||||
|
{
|
||||||
|
if (filled)
|
||||||
|
{
|
||||||
|
const auto r = 0.5f * rect_w / 2.0f;
|
||||||
|
const auto p0 = rect_center - ImVec2(r, r);
|
||||||
|
const auto p1 = rect_center + ImVec2(r, r);
|
||||||
|
|
||||||
|
#if IMGUI_VERSION_NUM > 18101
|
||||||
|
drawList->AddRectFilled(p0, p1, color, 0, ImDrawFlags_RoundCornersAll);
|
||||||
|
#else
|
||||||
|
drawList->AddRectFilled(p0, p1, color, 0, 15);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto r = 0.5f * rect_w / 2.0f - 0.5f;
|
||||||
|
const auto p0 = rect_center - ImVec2(r, r);
|
||||||
|
const auto p1 = rect_center + ImVec2(r, r);
|
||||||
|
|
||||||
|
if (innerColor & 0xFF000000)
|
||||||
|
{
|
||||||
|
#if IMGUI_VERSION_NUM > 18101
|
||||||
|
drawList->AddRectFilled(p0, p1, innerColor, 0, ImDrawFlags_RoundCornersAll);
|
||||||
|
#else
|
||||||
|
drawList->AddRectFilled(p0, p1, innerColor, 0, 15);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if IMGUI_VERSION_NUM > 18101
|
||||||
|
drawList->AddRect(p0, p1, color, 0, ImDrawFlags_RoundCornersAll, 2.0f * outline_scale);
|
||||||
|
#else
|
||||||
|
drawList->AddRect(p0, p1, color, 0, 15, 2.0f * outline_scale);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == IconType::Grid)
|
||||||
|
{
|
||||||
|
const auto r = 0.5f * rect_w / 2.0f;
|
||||||
|
const auto w = ceilf(r / 3.0f);
|
||||||
|
|
||||||
|
const auto baseTl = ImVec2(floorf(rect_center_x - w * 2.5f), floorf(rect_center_y - w * 2.5f));
|
||||||
|
const auto baseBr = ImVec2(floorf(baseTl.x + w), floorf(baseTl.y + w));
|
||||||
|
|
||||||
|
auto tl = baseTl;
|
||||||
|
auto br = baseBr;
|
||||||
|
for (int i = 0; i < 3; ++i)
|
||||||
|
{
|
||||||
|
tl.x = baseTl.x;
|
||||||
|
br.x = baseBr.x;
|
||||||
|
drawList->AddRectFilled(tl, br, color);
|
||||||
|
tl.x += w * 2;
|
||||||
|
br.x += w * 2;
|
||||||
|
if (i != 1 || filled)
|
||||||
|
drawList->AddRectFilled(tl, br, color);
|
||||||
|
tl.x += w * 2;
|
||||||
|
br.x += w * 2;
|
||||||
|
drawList->AddRectFilled(tl, br, color);
|
||||||
|
|
||||||
|
tl.y += w * 2;
|
||||||
|
br.y += w * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
triangleStart = br.x + w + 1.0f / 24.0f * rect_w;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == IconType::RoundSquare)
|
||||||
|
{
|
||||||
|
if (filled)
|
||||||
|
{
|
||||||
|
const auto r = 0.5f * rect_w / 2.0f;
|
||||||
|
const auto cr = r * 0.5f;
|
||||||
|
const auto p0 = rect_center - ImVec2(r, r);
|
||||||
|
const auto p1 = rect_center + ImVec2(r, r);
|
||||||
|
|
||||||
|
#if IMGUI_VERSION_NUM > 18101
|
||||||
|
drawList->AddRectFilled(p0, p1, color, cr, ImDrawFlags_RoundCornersAll);
|
||||||
|
#else
|
||||||
|
drawList->AddRectFilled(p0, p1, color, cr, 15);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto r = 0.5f * rect_w / 2.0f - 0.5f;
|
||||||
|
const auto cr = r * 0.5f;
|
||||||
|
const auto p0 = rect_center - ImVec2(r, r);
|
||||||
|
const auto p1 = rect_center + ImVec2(r, r);
|
||||||
|
|
||||||
|
if (innerColor & 0xFF000000)
|
||||||
|
{
|
||||||
|
#if IMGUI_VERSION_NUM > 18101
|
||||||
|
drawList->AddRectFilled(p0, p1, innerColor, cr, ImDrawFlags_RoundCornersAll);
|
||||||
|
#else
|
||||||
|
drawList->AddRectFilled(p0, p1, innerColor, cr, 15);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if IMGUI_VERSION_NUM > 18101
|
||||||
|
drawList->AddRect(p0, p1, color, cr, ImDrawFlags_RoundCornersAll, 2.0f * outline_scale);
|
||||||
|
#else
|
||||||
|
drawList->AddRect(p0, p1, color, cr, 15, 2.0f * outline_scale);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == IconType::Diamond)
|
||||||
|
{
|
||||||
|
if (filled)
|
||||||
|
{
|
||||||
|
const auto r = 0.607f * rect_w / 2.0f;
|
||||||
|
const auto c = rect_center;
|
||||||
|
|
||||||
|
drawList->PathLineTo(c + ImVec2( 0, -r));
|
||||||
|
drawList->PathLineTo(c + ImVec2( r, 0));
|
||||||
|
drawList->PathLineTo(c + ImVec2( 0, r));
|
||||||
|
drawList->PathLineTo(c + ImVec2(-r, 0));
|
||||||
|
drawList->PathFillConvex(color);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto r = 0.607f * rect_w / 2.0f - 0.5f;
|
||||||
|
const auto c = rect_center;
|
||||||
|
|
||||||
|
drawList->PathLineTo(c + ImVec2( 0, -r));
|
||||||
|
drawList->PathLineTo(c + ImVec2( r, 0));
|
||||||
|
drawList->PathLineTo(c + ImVec2( 0, r));
|
||||||
|
drawList->PathLineTo(c + ImVec2(-r, 0));
|
||||||
|
|
||||||
|
if (innerColor & 0xFF000000)
|
||||||
|
drawList->AddConvexPolyFilled(drawList->_Path.Data, drawList->_Path.Size, innerColor);
|
||||||
|
|
||||||
|
drawList->PathStroke(color, true, 2.0f * outline_scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto triangleTip = triangleStart + rect_w * (0.45f - 0.32f);
|
||||||
|
|
||||||
|
drawList->AddTriangleFilled(
|
||||||
|
ImVec2(ceilf(triangleTip), rect_y + rect_h * 0.5f),
|
||||||
|
ImVec2(triangleStart, rect_center_y + 0.15f * rect_h),
|
||||||
|
ImVec2(triangleStart, rect_center_y - 0.15f * rect_h),
|
||||||
|
color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/AnimGraphEditor/utilities/drawing.h
Normal file
12
src/AnimGraphEditor/utilities/drawing.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# pragma once
|
||||||
|
# include <imgui.h>
|
||||||
|
|
||||||
|
namespace ax {
|
||||||
|
namespace Drawing {
|
||||||
|
|
||||||
|
enum class IconType: ImU32 { Flow, Circle, Square, Grid, RoundSquare, Diamond };
|
||||||
|
|
||||||
|
void DrawIcon(ImDrawList* drawList, const ImVec2& a, const ImVec2& b, IconType type, bool filled, ImU32 color, ImU32 innerColor);
|
||||||
|
|
||||||
|
} // namespace Drawing
|
||||||
|
} // namespace ax
|
16
src/AnimGraphEditor/utilities/widgets.cpp
Normal file
16
src/AnimGraphEditor/utilities/widgets.cpp
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# define IMGUI_DEFINE_MATH_OPERATORS
|
||||||
|
# include "widgets.h"
|
||||||
|
# include <imgui_internal.h>
|
||||||
|
|
||||||
|
void ax::Widgets::Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color/* = ImVec4(1, 1, 1, 1)*/, const ImVec4& innerColor/* = ImVec4(0, 0, 0, 0)*/)
|
||||||
|
{
|
||||||
|
if (ImGui::IsRectVisible(size))
|
||||||
|
{
|
||||||
|
auto cursorPos = ImGui::GetCursorScreenPos();
|
||||||
|
auto drawList = ImGui::GetWindowDrawList();
|
||||||
|
ax::Drawing::DrawIcon(drawList, cursorPos, cursorPos + size, type, filled, ImColor(color), ImColor(innerColor));
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Dummy(size);
|
||||||
|
}
|
||||||
|
|
13
src/AnimGraphEditor/utilities/widgets.h
Normal file
13
src/AnimGraphEditor/utilities/widgets.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <imgui.h>
|
||||||
|
#include "drawing.h"
|
||||||
|
|
||||||
|
namespace ax {
|
||||||
|
namespace Widgets {
|
||||||
|
|
||||||
|
using Drawing::IconType;
|
||||||
|
|
||||||
|
void Icon(const ImVec2& size, IconType type, bool filled, const ImVec4& color = ImVec4(1, 1, 1, 1), const ImVec4& innerColor = ImVec4(0, 0, 0, 0));
|
||||||
|
|
||||||
|
} // namespace Widgets
|
||||||
|
} // namespace ax
|
13
src/main.cc
13
src/main.cc
@ -22,13 +22,13 @@
|
|||||||
#include "3rdparty/json/json.hpp"
|
#include "3rdparty/json/json.hpp"
|
||||||
#include "AnimGraph/AnimGraphBlendTree.h"
|
#include "AnimGraph/AnimGraphBlendTree.h"
|
||||||
#include "AnimGraph/AnimGraphData.h"
|
#include "AnimGraph/AnimGraphData.h"
|
||||||
|
#include "AnimGraphEditor/AnimGraphEditor.h"
|
||||||
#include "Camera.h"
|
#include "Camera.h"
|
||||||
#include "GLFW/glfw3.h"
|
#include "GLFW/glfw3.h"
|
||||||
#include "SkinnedMesh.h"
|
#include "SkinnedMesh.h"
|
||||||
#include "SkinnedMeshRenderer.h"
|
#include "SkinnedMeshRenderer.h"
|
||||||
#include "SkinnedMeshResource.h"
|
#include "SkinnedMeshResource.h"
|
||||||
#include "embedded_fonts.h"
|
#include "embedded_fonts.h"
|
||||||
#include "src/AnimGraph/AnimGraphEditor.h"
|
|
||||||
|
|
||||||
const int Width = 1024;
|
const int Width = 1024;
|
||||||
const int Height = 768;
|
const int Height = 768;
|
||||||
@ -159,12 +159,6 @@ struct Viewport {
|
|||||||
offscreen_pass_desc.label = "offscreen-pass";
|
offscreen_pass_desc.label = "offscreen-pass";
|
||||||
|
|
||||||
this->pass = sg_make_pass(&offscreen_pass_desc);
|
this->pass = sg_make_pass(&offscreen_pass_desc);
|
||||||
|
|
||||||
sg_pipeline_desc gl_pipeline_desc = {
|
|
||||||
.depth = {.compare = SG_COMPAREFUNC_LESS_EQUAL, .write_enabled = true},
|
|
||||||
.cull_mode = SG_CULLMODE_BACK,
|
|
||||||
.sample_count = cMSAASampleCount};
|
|
||||||
// this->pip = sg_make_pipeline(gl_pipeline_desc);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1063,7 +1057,12 @@ int main() {
|
|||||||
/* cleanup */
|
/* cleanup */
|
||||||
ax::NodeEditor::DestroyEditor(gApplicationConfig.graph_editor.context);
|
ax::NodeEditor::DestroyEditor(gApplicationConfig.graph_editor.context);
|
||||||
ImNodes::DestroyContext();
|
ImNodes::DestroyContext();
|
||||||
|
|
||||||
|
ImGui_ImplOpenGL3_Shutdown();
|
||||||
|
ImGui_ImplGlfw_Shutdown();
|
||||||
|
|
||||||
ImGui::DestroyContext();
|
ImGui::DestroyContext();
|
||||||
|
|
||||||
sgl_shutdown();
|
sgl_shutdown();
|
||||||
sg_shutdown();
|
sg_shutdown();
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#include "AnimGraph/AnimGraphEditor.h"
|
#include "AnimGraphEditor/AnimGraphEditor.h"
|
||||||
#include "catch.hpp"
|
#include "catch.hpp"
|
||||||
|
|
||||||
TEST_CASE("Node Socket To InputPin Conversion", "[animGraphEditor]") {
|
TEST_CASE("Node Socket To InputPin Conversion", "[animGraphEditor]") {
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "AnimGraph/AnimGraphBlendTree.h"
|
#include "AnimGraph/AnimGraphBlendTree.h"
|
||||||
#include "AnimGraph/AnimGraphEditor.h"
|
|
||||||
#include "AnimGraph/AnimGraphNodes.h"
|
#include "AnimGraph/AnimGraphNodes.h"
|
||||||
#include "AnimGraph/AnimGraphResource.h"
|
#include "AnimGraph/AnimGraphResource.h"
|
||||||
|
#include "AnimGraphEditor/AnimGraphEditor.h"
|
||||||
#include "catch.hpp"
|
#include "catch.hpp"
|
||||||
#include "ozz/base/io/archive.h"
|
#include "ozz/base/io/archive.h"
|
||||||
#include "ozz/base/io/stream.h"
|
#include "ozz/base/io/stream.h"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user