Using bits of the blueprints examples for the AnimGraphEditor.

This commit is contained in:
Martin Felis 2025-02-16 22:12:24 +01:00
parent a467715ce3
commit 5a6ac92a48
15 changed files with 968 additions and 193 deletions

View File

@ -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})

View File

View 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++) {

View File

@ -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;
} }

View File

@ -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();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2));
if (ImGui::BeginTable(
"split",
2,
ImGuiTableFlags_BordersOuter | ImGuiTableFlags_Resizable)) {
while (iter != outputs.end()) { while (iter != outputs.end()) {
Socket& output = *iter; Socket& socket = *iter;
ImGui::PushID(&output); ImGui::PushID(&socket);
if (NodeSocketEditor(output)) {
AnimGraphResource* current_graph_resource = ImGui::TableNextRow();
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex]; ImGui::TableSetColumnIndex(0);
current_graph_resource->m_socket_accessor->m_inputs = outputs; ImGui::AlignTextToFramePadding();
bool node_open = ImGui::TreeNode(socket.m_name.c_str());
ImGui::TableSetColumnIndex(1);
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();
} }
if (ImGui::Button("X")) { ImGui::PopID();
RemoveBlendTreeConnectionsForSocket(
blend_tree_resource,
node_resource,
output);
iter = outputs.erase(iter);
} else {
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);
//
// 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 void HandleConnectionCreation(
std::vector<Socket> node_outputs = BlendTreeResource& current_blend_tree) { // Create Connections
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,70 +573,138 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) {
} }
} }
ax::NodeEditor::EndCreate(); ax::NodeEditor::EndCreate();
// Popup menu
{
const bool open_popup = ImGui::IsMouseReleased(ImGuiMouseButton_Right);
if (open_popup && ImGui::IsWindowHovered()) {
ax::NodeEditor::Suspend();
ImGui::OpenPopup("add node");
ax::NodeEditor::Resume();
sEditorState.mousePopupStart = ImGui::GetMousePos();
} }
ax::NodeEditor::Suspend(); void BlendTreeRenderNodes(
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.f, 8.f)); BlendTreeResource& current_blend_tree,
if (ImGui::BeginPopup("add node")) { ax::NodeEditor::Utilities::BlueprintNodeBuilder& builder) {
std::string node_type_name = ""; for (size_t node_index = 0, n = current_blend_tree.GetNumNodes();
if (ImGui::MenuItem("AnimSampler")) { node_index < n;
node_type_name = "AnimSampler"; node_index++) {
} AnimNodeResource* node_resource = current_blend_tree.GetNode(node_index);
if (ImGui::MenuItem("Blend2")) { ax::NodeEditor::NodeId node_id(node_resource);
node_type_name = "Blend2";
}
if (ImGui::MenuItem("SpeedScale")) { builder.Begin(node_id);
node_type_name = "SpeedScale";
}
if (ImGui::MenuItem("LockTranslationNode")) { if (sEditorState.isGraphLoadedThisFrame) {
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::SetNodePosition(
ax::NodeEditor::NodeId(node_resource), node_id,
sEditorState.mousePopupStart); ImVec2(node_resource->m_position[0], node_resource->m_position[1]));
sEditorState.hierarchyStack[sEditorState.hierarchyStackIndex]
->m_blend_tree_resource.AddNode(node_resource);
} }
ImGui::EndPopup(); builder.Header();
ImGui::Text("%s", node_resource->m_node_type_name.c_str());
ImGui::Spring(0);
builder.EndHeader();
// 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];
builder.Input(NodeIndexAndSocketIndexToInputPinId(
static_cast<int>(node_index),
static_cast<int>(j)));
DrawSocketIcon(
socket.m_type,
current_blend_tree.IsSocketConnected(node_resource, socket.m_name));
ImGui::Spring(0);
//ImGui::PushItemWidth(100.0f);
ImGui::Text("%s", socket.m_name.c_str());
//ImGui::PopItemWidth();
builder.EndInput();
} }
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();
// //

View 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));
}

View 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

View 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);
}
}
}

View 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

View 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);
}

View 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

View File

@ -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();

View File

@ -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]") {

View File

@ -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"