Implemented Animation Clip asset and animation controller #410

Merged
XiaoQiDigipen merged 66 commits from SP3-22-AnimationController into main 2023-03-09 16:19:40 +08:00
5 changed files with 334 additions and 88 deletions
Showing only changes of commit 32adb0c540 - Show all commits

View File

@ -103,10 +103,10 @@ namespace SHADE
/*-------------------------------------------------------------------------------*/
/* Data Members */
/*-------------------------------------------------------------------------------*/
Handle<Node> Target;
ConditionType Condition;
AnimParam Param;
std::string ParamName;
Handle<Node> Target;
/*-------------------------------------------------------------------------------*/
/* Usage Functions */

View File

@ -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<Handle<SHAnimationController::Node>, int> inputAttribCount;
std::unordered_map<Handle<SHAnimationController::Node>, 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<char, 255> 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<char, 255> 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<uint16_t>(sourceAttrib);
destAttribIndex.Raw = static_cast<uint16_t>(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<int8_t>::lowest();
return extraInputAttrib;
}
SHAnimationControllerEditor::NodeAttributeIndex SHAnimationControllerEditor::getExtraOutputAttrib(uint32_t nodeIndex)
{
NodeAttributeIndex extraOutputAttrib;
extraOutputAttrib.OwnerNodeIndex = nodeIndex;
extraOutputAttrib.AttributeIndex = std::numeric_limits<int8_t>::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<SHAnimationControllerEditor::Node>::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<Node>::iterator sourceNode, std::list<Node>::iterator destNode)
{
// Update source node's output attributes
NodeAttributeIndex attribIndex;
attribIndex.OwnerNodeIndex = sourceNode->Index;
attribIndex.AttributeIndex = static_cast<int8_t>(sourceNode->OutputAttribs.size() + 1);
sourceNode->OutputAttribs.emplace_back(attribIndex);
// Update target node's input attributes
attribIndex.OwnerNodeIndex = destNode->Index;
attribIndex.AttributeIndex = static_cast<int8_t>(-(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<Handle<SHAnimationController::Node>, std::list<Node>::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 {};
}
}

View File

@ -13,6 +13,9 @@ of DigiPen Institute of Technology is prohibited.
// STL Includes
#include <vector>
#include <optional>
// External Dependencies
#include <imnodes.h>
// Project Includes
#include "Resource/SHHandle.h"
#include "Editor/EditorWindow/SHEditorWindow.h"
@ -39,22 +42,28 @@ namespace SHADE
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<SHAnimationClip> Clip;
std::vector<NodeAttributeIndex> InputAttribs;
std::vector<NodeAttributeIndex> OutputAttribs;
std::vector<NodeLinkIndex> Transitions;
bool EditingName = false;
};
struct LinkData
{
std::list<Node>::iterator SourceNode;
std::list<Node>::iterator TargetNode;
NodeAttributeIndex SourceAttrib;
NodeAttributeIndex DestAttrib;
};
struct AnimControllerData
{
std::list<Node> Nodes;
std::unordered_map<std::string, SHAnimationController::AnimParam::Type> Params;
std::unordered_map<uint32_t, LinkData> Links;
int NextNodeIndex = 0; // Index to use for newly created nodes
std::unordered_map<NodeIndex, std::list<Node>::iterator> IndexToNodeMap;
};
/*---------------------------------------------------------------------------------*/
/* Data Members */
/*---------------------------------------------------------------------------------*/
SHAnimationController controller;
std::vector<std::vector<NodeAttributeIndex>> inputNodesMap;
std::vector<std::vector<NodeAttributeIndex>> outputNodesMap;
std::vector<NodeLinkIndex> linkIndices; // Encodes details of the link in the node index
std::optional<AnimControllerData> 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<Node>::iterator createNode(AnimControllerData& data);
static void createLink(AnimControllerData& data, std::list<Node>::iterator sourceNode, std::list<Node>::iterator destNode);
static AnimControllerData deserialise(const SHAnimationController& controller);
static SHAnimationController serialise(const AnimControllerData& data);
};
}

View File

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

View File

@ -90,7 +90,7 @@ namespace SHADE
/// <returns>True if the header is open, false otherwise.</returns>
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
/// <param name="title">Text to display.</param>
static void Text(const std::string& title);
/// <summary>
/// Renders a text widget that is vertically and horizontally centered in the current
/// window.
/// </summary>
/// <param name="text">Text to display.</param>
static void CenteredText(const std::string& text);
/// <summary>
/// Creates a small inline button widget.
/// <br/>
/// Wraps up ImGui::SmallButton().