diff --git a/SHADE_Engine/src/Animation/SHAnimationController.h b/SHADE_Engine/src/Animation/SHAnimationController.h index ca6fbf70..a7acb772 100644 --- a/SHADE_Engine/src/Animation/SHAnimationController.h +++ b/SHADE_Engine/src/Animation/SHAnimationController.h @@ -103,10 +103,10 @@ namespace SHADE /*-------------------------------------------------------------------------------*/ /* Data Members */ /*-------------------------------------------------------------------------------*/ + Handle Target; ConditionType Condition; AnimParam Param; std::string ParamName; - Handle Target; /*-------------------------------------------------------------------------------*/ /* Usage Functions */ diff --git a/SHADE_Engine/src/Editor/EditorWindow/Animation/SHAnimationControllerEditor.cpp b/SHADE_Engine/src/Editor/EditorWindow/Animation/SHAnimationControllerEditor.cpp index 31a38121..84db473c 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/Animation/SHAnimationControllerEditor.cpp +++ b/SHADE_Engine/src/Editor/EditorWindow/Animation/SHAnimationControllerEditor.cpp @@ -38,6 +38,25 @@ namespace SHADE void SHAnimationControllerEditor::Init() { SHEditorWindow::Init(); + + SHAnimationController controller; + auto n1 = controller.CreateNode(); + auto n2 = controller.CreateNode(); + auto n3 = controller.CreateNode(); + + n1->Name = "N1"; + n2->Name = "N2"; + n3->Name = "N3"; + + SHAnimationController::Transition t; + t.Target = n3; + + n1->Transitions.emplace_back(t); + n2->Transitions.emplace_back(t); + t.Target = n1; + n3->Transitions.emplace_back(t); + + Open(controller); } void SHAnimationControllerEditor::Update() @@ -46,79 +65,17 @@ namespace SHADE if (Begin()) { - drawMenuBar(); - - ImNodes::BeginNodeEditor(); + + // Only render the node editor if there is controller data + if (controllerData.has_value()) { - /* Pre Process Nodes */ - std::unordered_map, int> inputAttribCount; - std::unordered_map, int> outputAttribCount; - for (auto node : controller.GetNodes()) - { - // Have at least one for each - inputAttribCount[node] = 1; - outputAttribCount[node] = 1; - } - for (auto node : controller.GetNodes()) - { - for (auto transition : node->Transitions) - { - ++inputAttribCount[transition.Target]; - } - outputAttribCount[node] += node->Transitions.size(); - } - - /* Draw Nodes */ - int id = 0; - for (auto node : controller.GetNodes()) - { - // Draw the node - ImNodes::BeginNode(id); - { - // Title - ImNodes::BeginNodeTitleBar(); - { - ImGui::TextUnformatted(node->Name.c_str()); - ImGui::SameLine(); - ImGui::Button(ICON_MD_EDIT); - } - ImNodes::EndNodeTitleBar(); - - // Body - ImGui::Text("Clip"); - ImGui::SameLine(); - std::array buffer = { '\0' }; - ImGui::PushItemWidth(80.0f); - ImGui::InputText("", buffer.data(), buffer.size()); - ImGui::PopItemWidth(); - - // Input Nodes - for (int i = 0; i < inputAttribCount[node]; ++i) - { - NodeAttributeIndex nidx; - nidx.OwnerNodeIndex = id; - nidx.AttributeIndex = i; - ImNodes::BeginInputAttribute(nidx.Raw); - ImNodes::EndInputAttribute(); - } - - // Output Nodes - for (int i = 0; i < outputAttribCount[node]; ++i) - { - NodeAttributeIndex nidx; - nidx.OwnerNodeIndex = id; - nidx.AttributeIndex = inputAttribCount[node] + i; - ImNodes::BeginOutputAttribute(nidx.Raw); - ImNodes::EndOutputAttribute(); - } - } - ImNodes::EndNode(); - ++id; - } + drawActiveMenuBar(); + drawNodeEditor(); + } + else + { + SHEditorUI::CenteredText("No animation controller is selected."); } - - ImNodes::MiniMap(0.2f, ImNodesMiniMapLocation_BottomRight); - ImNodes::EndNodeEditor(); } ImGui::End(); } @@ -128,19 +85,246 @@ namespace SHADE SHEditorWindow::Exit(); } + void SHAnimationControllerEditor::Open(const SHAnimationController& controller) + { + controllerData = deserialise(controller); + } /*-----------------------------------------------------------------------------------*/ /* Helper Functions */ /*-----------------------------------------------------------------------------------*/ - void SHAnimationControllerEditor::drawMenuBar() + void SHAnimationControllerEditor::drawActiveMenuBar() { if (ImGui::BeginMenuBar()) { + if (ImGui::Button(std::format("{} Save", ICON_MD_SAVE).data())) + { + // TODO + } if (ImGui::Button(std::format("{} Add", ICON_MD_ADD).data())) { - controller.CreateNode(); + createNode(controllerData.value()); } ImGui::EndMenuBar(); } } + + void SHAnimationControllerEditor::drawNodeEditor() + { + static constexpr float NODE_WIDTH = 80.0f; + + ImNodes::BeginNodeEditor(); + { + /* Draw Nodes */ + for (auto& node : controllerData->Nodes) + { + // Draw the node + ImNodes::BeginNode(node.Index); + { + // Title + ImNodes::BeginNodeTitleBar(); + { + if (node.EditingName) + { + if (ImGui::Button(ICON_MD_DONE)) + { + node.EditingName = false; + } + ImGui::SameLine(); + static constexpr float TEXT_FIELD_PADDING = 15.0f; + ImGui::SetNextItemWidth(std::max(ImGui::CalcTextSize(node.Name.c_str()).x + TEXT_FIELD_PADDING, NODE_WIDTH)); + SHEditorUI::InputTextField("", node.Name); + } + else + { + if (ImGui::Button(ICON_MD_EDIT)) + { + node.EditingName = true; + } + ImGui::SameLine(); + ImGui::Text(node.Name.c_str()); + } + } + ImNodes::EndNodeTitleBar(); + + // Body + ImGui::Text("Clip"); + ImGui::SameLine(); + std::array buffer = { '\0' }; + ImGui::SetNextItemWidth(std::max(NODE_WIDTH, ImGui::CalcTextSize(buffer.data()).x)); + ImGui::InputText("", buffer.data(), buffer.size()); + + // Input Nodes + for (auto inputAttrib : node.InputAttribs) + { + drawInputNode(inputAttrib.Raw, ImNodesPinShape_CircleFilled); + } + + // Render an extra input + drawInputNode(getExtraInputAttrib(node.Index).Raw, ImNodesPinShape_Circle); + + // Output Nodes + for (auto outputAttrib : node.OutputAttribs) + { + drawOutputNode(outputAttrib.Raw, node.Index, ImNodesPinShape_TriangleFilled); + } + + // Render an extra output + drawOutputNode(getExtraOutputAttrib(node.Index).Raw, node.Index, ImNodesPinShape_Triangle); + } + ImNodes::EndNode(); + } + + // Draw links + for (auto link : controllerData->Links) + { + ImNodes::Link(link.first, link.second.SourceAttrib.Raw, link.second.DestAttrib.Raw); + } + } + + ImNodes::MiniMap(0.2f, ImNodesMiniMapLocation_BottomRight); + ImNodes::EndNodeEditor(); + + int sourceAttrib, destAttrib; + if (ImNodes::IsLinkCreated(&sourceAttrib, &destAttrib)) + { + // Get the two indices + NodeAttributeIndex sourceAttribIndex, destAttribIndex; + sourceAttribIndex.Raw = static_cast(sourceAttrib); + destAttribIndex.Raw = static_cast(destAttrib); + + // Ensure that we can access the nodes + if (controllerData->IndexToNodeMap.contains(sourceAttribIndex.OwnerNodeIndex) && + controllerData->IndexToNodeMap.contains(destAttribIndex.OwnerNodeIndex)) + { + // Retrieve the nodes + auto inputNodeIter = controllerData->IndexToNodeMap[sourceAttribIndex.OwnerNodeIndex]; + auto outputNodeIter = *controllerData->IndexToNodeMap[destAttribIndex.OwnerNodeIndex]; + + // Create link + createLink + ( + controllerData.value(), + controllerData->IndexToNodeMap[sourceAttribIndex.OwnerNodeIndex], + controllerData->IndexToNodeMap[destAttribIndex.OwnerNodeIndex] + ); + } + } + } + SHAnimationControllerEditor::NodeAttributeIndex SHAnimationControllerEditor::getExtraInputAttrib(uint32_t nodeIndex) + { + NodeAttributeIndex extraInputAttrib; + extraInputAttrib.OwnerNodeIndex = nodeIndex; + extraInputAttrib.AttributeIndex = std::numeric_limits::lowest(); + return extraInputAttrib; + } + SHAnimationControllerEditor::NodeAttributeIndex SHAnimationControllerEditor::getExtraOutputAttrib(uint32_t nodeIndex) + { + NodeAttributeIndex extraOutputAttrib; + extraOutputAttrib.OwnerNodeIndex = nodeIndex; + extraOutputAttrib.AttributeIndex = std::numeric_limits::max(); + return extraOutputAttrib; + } + void SHAnimationControllerEditor::drawInputNode(int id, ImNodesPinShape_ pinShape) + { + ImNodes::BeginInputAttribute(id, pinShape); + ImGui::Text("Input"); + ImNodes::EndInputAttribute(); + } + void SHAnimationControllerEditor::drawOutputNode(int id, int parentNodeId, ImNodesPinShape_ pinShape) + { + static char const* TITLE = "Output"; + static float RIGHT_PADDING = 20.0f; + + ImNodes::BeginOutputAttribute(id, ImNodesPinShape_TriangleFilled); + ImGui::Indent(ImNodes::GetNodeDimensions(parentNodeId).x - ImGui::CalcTextSize(TITLE).x - RIGHT_PADDING); + ImGui::Text(TITLE); + ImNodes::EndOutputAttribute(); + } + std::list::iterator SHAnimationControllerEditor::createNode(AnimControllerData& data) + { + Node localNode; + localNode.Index = data.NextNodeIndex++; + data.Nodes.emplace_back(std::move(localNode)); + + // Update the node map + auto nodeIter = --data.Nodes.end(); + data.IndexToNodeMap[localNode.Index] = nodeIter; + + return nodeIter; + } + void SHAnimationControllerEditor::createLink(AnimControllerData& data, std::list::iterator sourceNode, std::list::iterator destNode) + { + // Update source node's output attributes + NodeAttributeIndex attribIndex; + attribIndex.OwnerNodeIndex = sourceNode->Index; + attribIndex.AttributeIndex = static_cast(sourceNode->OutputAttribs.size() + 1); + sourceNode->OutputAttribs.emplace_back(attribIndex); + + // Update target node's input attributes + attribIndex.OwnerNodeIndex = destNode->Index; + attribIndex.AttributeIndex = static_cast(-(destNode->InputAttribs.size() + 1)); + destNode->InputAttribs.emplace_back(attribIndex); + + // Create link + LinkData link; + link.SourceNode = sourceNode; + link.TargetNode = destNode; + link.SourceAttrib = sourceNode->OutputAttribs.back(); + link.DestAttrib = destNode->InputAttribs.back(); + NodeLinkIndex linkIdx; + linkIdx.SourceAttribute = link.SourceAttrib; + linkIdx.DestinationAttribute = link.DestAttrib; + + data.Links.emplace(linkIdx.Raw, std::move(link)); + sourceNode->Transitions.emplace_back(linkIdx); + } + /*-----------------------------------------------------------------------------------*/ + /* Static Helper Functions */ + /*-----------------------------------------------------------------------------------*/ + SHAnimationControllerEditor::AnimControllerData SHAnimationControllerEditor::deserialise(const SHAnimationController& controller) + { + AnimControllerData data; + + // Maps controller nodes to data nodes + std::unordered_map, std::list::iterator> nodeMap; + + // Load anim parameters + data.Params = controller.GetParams(); + + // Load nodes and links + for (auto node : controller.GetNodes()) + { + auto localNode = createNode(data); + localNode->Name = node->Name; + localNode->Clip = node->Clip; + nodeMap.emplace(node, localNode); + } + + // Load links + for (auto node : controller.GetNodes()) + { + // Get the corresponding data node + auto dataNodeIter = nodeMap[node]; + + for (auto transition : node->Transitions) + { + // Invalid node check + if (!nodeMap.contains(transition.Target)) + continue; + + // Get the target node + auto targetNodeIter = nodeMap[transition.Target]; + + // Create link + createLink(data, dataNodeIter, targetNodeIter); + } + } + + return data; + } + SHAnimationController SHAnimationControllerEditor::serialise(const AnimControllerData& data) + { + return {}; + } } \ No newline at end of file diff --git a/SHADE_Engine/src/Editor/EditorWindow/Animation/SHAnimationControllerEditor.h b/SHADE_Engine/src/Editor/EditorWindow/Animation/SHAnimationControllerEditor.h index a78d2fd6..d12e55cf 100644 --- a/SHADE_Engine/src/Editor/EditorWindow/Animation/SHAnimationControllerEditor.h +++ b/SHADE_Engine/src/Editor/EditorWindow/Animation/SHAnimationControllerEditor.h @@ -13,6 +13,9 @@ of DigiPen Institute of Technology is prohibited. // STL Includes #include +#include +// External Dependencies +#include // Project Includes #include "Resource/SHHandle.h" #include "Editor/EditorWindow/SHEditorWindow.h" @@ -38,23 +41,29 @@ namespace SHADE void Init() override; void Update() override; void Exit() override; + + /*---------------------------------------------------------------------------------*/ + /* Usage Functions */ + /*---------------------------------------------------------------------------------*/ + void Open(const SHAnimationController& controller); private: /*---------------------------------------------------------------------------------*/ /* Type Definitions */ /*---------------------------------------------------------------------------------*/ + using NodeIndex = uint8_t; union NodeAttributeIndex { - int16_t Raw; + uint16_t Raw; struct { - uint8_t OwnerNodeIndex; - uint8_t AttributeIndex; + NodeIndex OwnerNodeIndex; + int8_t AttributeIndex; // Negative is input, positive is output }; }; union NodeLinkIndex { - int32_t Raw; + uint32_t Raw; struct { NodeAttributeIndex SourceAttribute; @@ -62,17 +71,55 @@ namespace SHADE }; }; + struct Node + { + NodeIndex Index; + std::string Name = "Unnamed Node"; + Handle Clip; + std::vector InputAttribs; + std::vector OutputAttribs; + std::vector Transitions; + bool EditingName = false; + }; + + struct LinkData + { + std::list::iterator SourceNode; + std::list::iterator TargetNode; + NodeAttributeIndex SourceAttrib; + NodeAttributeIndex DestAttrib; + }; + + struct AnimControllerData + { + std::list Nodes; + std::unordered_map Params; + std::unordered_map Links; + int NextNodeIndex = 0; // Index to use for newly created nodes + std::unordered_map::iterator> IndexToNodeMap; + }; + /*---------------------------------------------------------------------------------*/ /* Data Members */ /*---------------------------------------------------------------------------------*/ - SHAnimationController controller; - std::vector> inputNodesMap; - std::vector> outputNodesMap; - std::vector linkIndices; // Encodes details of the link in the node index + std::optional controllerData; /*---------------------------------------------------------------------------------*/ /* Helper Functions */ /*---------------------------------------------------------------------------------*/ - void drawMenuBar(); + void drawActiveMenuBar(); + void drawNodeEditor(); + NodeAttributeIndex getExtraInputAttrib(uint32_t nodeIndex); + NodeAttributeIndex getExtraOutputAttrib(uint32_t nodeIndex); + void drawInputNode(int id, ImNodesPinShape_ pinShape); + void drawOutputNode(int id, int parentNodeId, ImNodesPinShape_ pinShape); + + /*---------------------------------------------------------------------------------*/ + /* Static Helper Functions */ + /*---------------------------------------------------------------------------------*/ + static std::list::iterator createNode(AnimControllerData& data); + static void createLink(AnimControllerData& data, std::list::iterator sourceNode, std::list::iterator destNode); + static AnimControllerData deserialise(const SHAnimationController& controller); + static SHAnimationController serialise(const AnimControllerData& data); }; -} \ No newline at end of file +} diff --git a/SHADE_Engine/src/Editor/SHEditorUI.cpp b/SHADE_Engine/src/Editor/SHEditorUI.cpp index b9783020..3096759f 100644 --- a/SHADE_Engine/src/Editor/SHEditorUI.cpp +++ b/SHADE_Engine/src/Editor/SHEditorUI.cpp @@ -137,6 +137,15 @@ namespace SHADE { ImGui::Text(title.c_str()); } + void SHEditorUI::CenteredText(const std::string& text) + { + const auto WINDOW_SIZE = ImGui::GetWindowSize(); + const auto TEXT_SIZE = ImGui::CalcTextSize(text.c_str()); + + ImGui::SetCursorPosX((WINDOW_SIZE.x - TEXT_SIZE.x) * 0.5f); + ImGui::SetCursorPosY((WINDOW_SIZE.y - TEXT_SIZE.y) * 0.5f); + ImGui::Text(text.c_str()); + } bool SHEditorUI::SmallButton(const std::string& title) { return ImGui::SmallButton(title.c_str()); diff --git a/SHADE_Engine/src/Editor/SHEditorUI.h b/SHADE_Engine/src/Editor/SHEditorUI.h index 94804bb9..8d4a7b6e 100644 --- a/SHADE_Engine/src/Editor/SHEditorUI.h +++ b/SHADE_Engine/src/Editor/SHEditorUI.h @@ -90,7 +90,7 @@ namespace SHADE /// True if the header is open, false otherwise. static bool CollapsingHeader(const std::string& title, bool* isHovered = nullptr); static void SameLine(); - static void Separator(); + static void Separator(); /*-----------------------------------------------------------------------------*/ /* ImGui Wrapper Functions - Queries */ @@ -98,9 +98,9 @@ namespace SHADE static bool IsItemHovered(); /*-----------------------------------------------------------------------------*/ - /* ImGui Wrapper Functions - Menu */ - /*-----------------------------------------------------------------------------*/ - static bool BeginMenu(const std::string& label); + /* ImGui Wrapper Functions - Menu */ + /*-----------------------------------------------------------------------------*/ + static bool BeginMenu(const std::string& label); static bool BeginMenu(const std::string& label, const char* icon); static void EndMenu(); static void BeginTooltip(); @@ -150,6 +150,12 @@ namespace SHADE /// Text to display. static void Text(const std::string& title); /// + /// Renders a text widget that is vertically and horizontally centered in the current + /// window. + /// + /// Text to display. + static void CenteredText(const std::string& text); + /// /// Creates a small inline button widget. ///
/// Wraps up ImGui::SmallButton().