Implemented Animation Clip asset and animation controller #410
|
@ -103,10 +103,10 @@ namespace SHADE
|
|||
/*-------------------------------------------------------------------------------*/
|
||||
/* Data Members */
|
||||
/*-------------------------------------------------------------------------------*/
|
||||
Handle<Node> Target;
|
||||
ConditionType Condition;
|
||||
AnimParam Param;
|
||||
std::string ParamName;
|
||||
Handle<Node> Target;
|
||||
|
||||
/*-------------------------------------------------------------------------------*/
|
||||
/* Usage Functions */
|
||||
|
|
|
@ -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 {};
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
}
|
|
@ -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());
|
||||
|
|
|
@ -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().
|
||||
|
|
Loading…
Reference in New Issue