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
2 changed files with 229 additions and 13 deletions
Showing only changes of commit d16f807a8a - Show all commits

View File

@ -22,6 +22,8 @@ of DigiPen Institute of Technology is prohibited.
#include "Editor/IconsMaterialDesign.h"
#include "Animation/SHAnimationController.h"
#include "Editor/SHEditorUI.h"
#include "Editor/SHEditorWidgets.hpp"
#include "../../Command/SHCommand.hpp"
namespace SHADE
{
@ -39,6 +41,19 @@ namespace SHADE
{
SHEditorWindow::Init();
// Set up caches
conditionsList =
{
"None",
"=",
"!=",
"<",
"<=",
">",
">="
};
// Set up sample animation controller for testing
SHAnimationController controller;
auto n1 = controller.CreateNode();
auto n2 = controller.CreateNode();
@ -90,7 +105,7 @@ namespace SHADE
ImGui::TableSetColumnIndex(1);
drawNodeEditor();
ImGui::TableSetColumnIndex(2);
ImGui::Text("Properties");
drawPropertiesPanel();
}
}
ImGui::EndTable();
@ -234,6 +249,188 @@ namespace SHADE
}
}
}
void SHAnimationControllerEditor::drawPropertiesPanel()
{
const int SELECTED_LINKS_COUNT = ImNodes::NumSelectedLinks();
if (SELECTED_LINKS_COUNT > 0)
{
std::vector<int> selectedLinks(SELECTED_LINKS_COUNT);
ImNodes::GetSelectedLinks(selectedLinks.data());
// Go through all links and display them
int index = 0;
for (int link : selectedLinks)
{
// Get LinkData
NodeLinkIndex nodeLinkIndex;
nodeLinkIndex.Raw = link;
if (!controllerData->Links.contains(nodeLinkIndex.Raw))
continue;
LinkData& linkData = controllerData->Links[nodeLinkIndex.Raw];
// Ensure that the link is valid
if (!controllerData->IndexToNodeMap.contains(nodeLinkIndex.SourceAttribute.OwnerNodeIndex) ||
!controllerData->IndexToNodeMap.contains(nodeLinkIndex.DestinationAttribute.OwnerNodeIndex))
{
continue;
}
// Create name of the link
std::ostringstream oss;
oss << controllerData->IndexToNodeMap[nodeLinkIndex.SourceAttribute.OwnerNodeIndex]->Name
<< " " << ICON_MD_ARROW_RIGHT_ALT << " "
<< controllerData->IndexToNodeMap[nodeLinkIndex.DestinationAttribute.OwnerNodeIndex]->Name;
ImGui::PushID(index++);
// Display each link
if (SHEditorUI::CollapsingHeader(oss.str()))
{
const bool IS_PARAM_SET = !linkData.ParamName.empty();
// Anim Parameter
ImGui::Text("Parameter");
ImGui::SameLine();
if (ImGui::BeginCombo("##Parameter", IS_PARAM_SET ? linkData.ParamName.c_str() : "None", ImGuiComboFlags_None))
{
// Initial "None" option
if (ImGui::Selectable("None", !IS_PARAM_SET))
{
SHCommandManager::PerformCommand
(
std::reinterpret_pointer_cast<SHBaseCommand>
(
std::make_shared<SHCommand<std::string>>
(
linkData.ParamName,
std::string{},
[&](const std::string& val) { linkData.ParamName = val; }
)
),
false
);
}
if (!IS_PARAM_SET)
{
ImGui::SetItemDefaultFocus();
}
// All other options
for (const auto& param : controllerData->Params)
{
const bool IS_SELECTED = param.first == linkData.ParamName;
if (ImGui::Selectable(param.first.c_str(), IS_SELECTED))
{
linkData.ParamName = param.first;
SHCommandManager::PerformCommand
(
std::reinterpret_pointer_cast<SHBaseCommand>
(
std::make_shared<SHCommand<std::string>>
(
linkData.ParamName,
param.first,
[&](const std::string& val) { linkData.ParamName = val; }
)
),
false
);
}
if (IS_SELECTED)
{
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
// Properties for an Animation Parameter
if (IS_PARAM_SET && controllerData->Params.contains(linkData.ParamName))
{
const SHAnimationController::AnimParam::Type PARAM_TYPE = controllerData->Params[linkData.ParamName];
// Comparison Type
const auto& CURR_COMPARISON = conditionsList[static_cast<int>(linkData.Condition)];
ImGui::Text("Condition Type");
ImGui::SameLine();
if (ImGui::BeginCombo("##ConditionType", CURR_COMPARISON.c_str(), ImGuiComboFlags_None))
{
// We only show equal and not equal for bool
const int LAST_ELEM = PARAM_TYPE == SHAnimationController::AnimParam::Type::Bool ? static_cast<int>(SHAnimationController::Transition::ConditionType::NotEquals)
: static_cast<int>(conditionsList.size() - 1);
// Comparisons
for (int i = 0; i <= LAST_ELEM; ++i)
{
const bool IS_SELECTED = i == static_cast<int>(linkData.Condition);
if (ImGui::Selectable(conditionsList[i].c_str(), IS_SELECTED))
{
SHCommandManager::PerformCommand
(
std::reinterpret_pointer_cast<SHBaseCommand>
(
std::make_shared<SHCommand<SHAnimationController::Transition::ConditionType>>
(
linkData.Condition,
static_cast<SHAnimationController::Transition::ConditionType>(i),
[&](SHAnimationController::Transition::ConditionType val) { linkData.Condition = val; }
)
),
false
);
}
if (IS_SELECTED)
{
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
// Parameter Value
switch (PARAM_TYPE)
{
case SHAnimationController::AnimParam::Type::Bool:
SHEditorWidgets::CheckBox
(
"Threshold",
[&](){ return std::get<bool>(linkData.ParamThresholdValue); },
[&](bool val) { linkData.ParamThresholdValue = val; }
);
break;
case SHAnimationController::AnimParam::Type::Float:
SHEditorWidgets::DragFloat
(
"Threshold",
[&]() { return std::get<float>(linkData.ParamThresholdValue); },
[&](float val) { linkData.ParamThresholdValue = val; }
);
break;
case SHAnimationController::AnimParam::Type::Int:
SHEditorWidgets::DragInt
(
"Threshold",
[&]() { return std::get<int>(linkData.ParamThresholdValue); },
[&](int val) { linkData.ParamThresholdValue = val; }
);
break;
}
}
}
ImGui::PopID();
}
}
else
{
ImGui::Text("Select an object to view properties.");
}
}
SHAnimationControllerEditor::NodeAttributeIndex SHAnimationControllerEditor::getExtraInputAttrib(uint32_t nodeIndex)
{
NodeAttributeIndex extraInputAttrib;
@ -266,17 +463,19 @@ namespace SHADE
}
std::list<SHAnimationControllerEditor::Node>::iterator SHAnimationControllerEditor::createNode(AnimControllerData& data)
{
const NodeIndex NEW_NODE_IDX = data.NextNodeIndex++;
Node localNode;
localNode.Index = data.NextNodeIndex++;
localNode.Index = NEW_NODE_IDX;
data.Nodes.emplace_back(std::move(localNode));
// Update the node map
auto nodeIter = --data.Nodes.end();
data.IndexToNodeMap[localNode.Index] = nodeIter;
data.IndexToNodeMap[NEW_NODE_IDX] = nodeIter;
return nodeIter;
}
void SHAnimationControllerEditor::createLink(AnimControllerData& data, std::list<Node>::iterator sourceNode, std::list<Node>::iterator destNode)
SHAnimationControllerEditor::LinkMap::iterator SHAnimationControllerEditor::createLink(AnimControllerData& data, std::list<Node>::iterator sourceNode, std::list<Node>::iterator destNode)
{
// Update source node's output attributes
NodeAttributeIndex attribIndex;
@ -299,8 +498,10 @@ namespace SHADE
linkIdx.SourceAttribute = link.SourceAttrib;
linkIdx.DestinationAttribute = link.DestAttrib;
data.Links.emplace(linkIdx.Raw, std::move(link));
const auto EMPLACE_DATA = data.Links.emplace(linkIdx.Raw, std::move(link));
sourceNode->Transitions.emplace_back(linkIdx);
return EMPLACE_DATA.first;
}
/*-----------------------------------------------------------------------------------*/
/* Static Helper Functions */
@ -340,7 +541,10 @@ namespace SHADE
auto targetNodeIter = nodeMap[transition.Target];
// Create link
createLink(data, dataNodeIter, targetNodeIter);
auto& linkData = createLink(data, dataNodeIter, targetNodeIter)->second;
linkData.Condition = transition.Condition;
linkData.ParamName = transition.ParamName;
linkData.ParamThresholdValue = transition.Param.Value;
}
}

View File

@ -52,6 +52,7 @@ namespace SHADE
/* Type Definitions */
/*---------------------------------------------------------------------------------*/
using NodeIndex = uint8_t;
using LinkIndex = int32_t;
union NodeAttributeIndex
{
uint16_t Raw;
@ -63,7 +64,7 @@ namespace SHADE
};
union NodeLinkIndex
{
uint32_t Raw;
LinkIndex Raw;
struct
{
NodeAttributeIndex SourceAttribute;
@ -84,17 +85,25 @@ namespace SHADE
struct LinkData
{
std::list<Node>::iterator SourceNode;
std::list<Node>::iterator TargetNode;
NodeAttributeIndex SourceAttrib;
NodeAttributeIndex DestAttrib;
// Source/Dest Data
std::list<Node>::iterator SourceNode;
std::list<Node>::iterator TargetNode;
NodeAttributeIndex SourceAttrib;
NodeAttributeIndex DestAttrib;
// Conditional Data
SHAnimationController::Transition::ConditionType Condition;
std::string ParamName;
SHAnimationController::AnimParam::ValueType ParamThresholdValue;
};
using LinkMap = std::unordered_map<LinkIndex, LinkData>;
struct AnimControllerData
{
std::list<Node> Nodes;
std::unordered_map<std::string, SHAnimationController::AnimParam::Type> Params;
std::unordered_map<uint32_t, LinkData> Links;
LinkMap Links;
int NextNodeIndex = 0; // Index to use for newly created nodes
std::unordered_map<NodeIndex, std::list<Node>::iterator> IndexToNodeMap;
};
@ -103,12 +112,15 @@ namespace SHADE
/* Data Members */
/*---------------------------------------------------------------------------------*/
std::optional<AnimControllerData> controllerData;
// Persistent Cached Data
std::vector<std::string> conditionsList;
/*---------------------------------------------------------------------------------*/
/* Helper Functions */
/*---------------------------------------------------------------------------------*/
void drawActiveMenuBar();
void drawNodeEditor();
void drawPropertiesPanel();
NodeAttributeIndex getExtraInputAttrib(uint32_t nodeIndex);
NodeAttributeIndex getExtraOutputAttrib(uint32_t nodeIndex);
void drawInputNode(int id, ImNodesPinShape_ pinShape);
@ -118,7 +130,7 @@ namespace SHADE
/* 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 LinkMap::iterator 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);
};