Implemented Animation Clip asset and animation controller #410
|
@ -22,6 +22,8 @@ of DigiPen Institute of Technology is prohibited.
|
||||||
#include "Editor/IconsMaterialDesign.h"
|
#include "Editor/IconsMaterialDesign.h"
|
||||||
#include "Animation/SHAnimationController.h"
|
#include "Animation/SHAnimationController.h"
|
||||||
#include "Editor/SHEditorUI.h"
|
#include "Editor/SHEditorUI.h"
|
||||||
|
#include "Editor/SHEditorWidgets.hpp"
|
||||||
|
#include "../../Command/SHCommand.hpp"
|
||||||
|
|
||||||
namespace SHADE
|
namespace SHADE
|
||||||
{
|
{
|
||||||
|
@ -39,6 +41,19 @@ namespace SHADE
|
||||||
{
|
{
|
||||||
SHEditorWindow::Init();
|
SHEditorWindow::Init();
|
||||||
|
|
||||||
|
// Set up caches
|
||||||
|
conditionsList =
|
||||||
|
{
|
||||||
|
"None",
|
||||||
|
"=",
|
||||||
|
"!=",
|
||||||
|
"<",
|
||||||
|
"<=",
|
||||||
|
">",
|
||||||
|
">="
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set up sample animation controller for testing
|
||||||
SHAnimationController controller;
|
SHAnimationController controller;
|
||||||
auto n1 = controller.CreateNode();
|
auto n1 = controller.CreateNode();
|
||||||
auto n2 = controller.CreateNode();
|
auto n2 = controller.CreateNode();
|
||||||
|
@ -90,7 +105,7 @@ namespace SHADE
|
||||||
ImGui::TableSetColumnIndex(1);
|
ImGui::TableSetColumnIndex(1);
|
||||||
drawNodeEditor();
|
drawNodeEditor();
|
||||||
ImGui::TableSetColumnIndex(2);
|
ImGui::TableSetColumnIndex(2);
|
||||||
ImGui::Text("Properties");
|
drawPropertiesPanel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndTable();
|
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)
|
SHAnimationControllerEditor::NodeAttributeIndex SHAnimationControllerEditor::getExtraInputAttrib(uint32_t nodeIndex)
|
||||||
{
|
{
|
||||||
NodeAttributeIndex extraInputAttrib;
|
NodeAttributeIndex extraInputAttrib;
|
||||||
|
@ -266,17 +463,19 @@ namespace SHADE
|
||||||
}
|
}
|
||||||
std::list<SHAnimationControllerEditor::Node>::iterator SHAnimationControllerEditor::createNode(AnimControllerData& data)
|
std::list<SHAnimationControllerEditor::Node>::iterator SHAnimationControllerEditor::createNode(AnimControllerData& data)
|
||||||
{
|
{
|
||||||
|
const NodeIndex NEW_NODE_IDX = data.NextNodeIndex++;
|
||||||
|
|
||||||
Node localNode;
|
Node localNode;
|
||||||
localNode.Index = data.NextNodeIndex++;
|
localNode.Index = NEW_NODE_IDX;
|
||||||
data.Nodes.emplace_back(std::move(localNode));
|
data.Nodes.emplace_back(std::move(localNode));
|
||||||
|
|
||||||
// Update the node map
|
// Update the node map
|
||||||
auto nodeIter = --data.Nodes.end();
|
auto nodeIter = --data.Nodes.end();
|
||||||
data.IndexToNodeMap[localNode.Index] = nodeIter;
|
data.IndexToNodeMap[NEW_NODE_IDX] = nodeIter;
|
||||||
|
|
||||||
return 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
|
// Update source node's output attributes
|
||||||
NodeAttributeIndex attribIndex;
|
NodeAttributeIndex attribIndex;
|
||||||
|
@ -299,8 +498,10 @@ namespace SHADE
|
||||||
linkIdx.SourceAttribute = link.SourceAttrib;
|
linkIdx.SourceAttribute = link.SourceAttrib;
|
||||||
linkIdx.DestinationAttribute = link.DestAttrib;
|
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);
|
sourceNode->Transitions.emplace_back(linkIdx);
|
||||||
|
|
||||||
|
return EMPLACE_DATA.first;
|
||||||
}
|
}
|
||||||
/*-----------------------------------------------------------------------------------*/
|
/*-----------------------------------------------------------------------------------*/
|
||||||
/* Static Helper Functions */
|
/* Static Helper Functions */
|
||||||
|
@ -340,7 +541,10 @@ namespace SHADE
|
||||||
auto targetNodeIter = nodeMap[transition.Target];
|
auto targetNodeIter = nodeMap[transition.Target];
|
||||||
|
|
||||||
// Create link
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ namespace SHADE
|
||||||
/* Type Definitions */
|
/* Type Definitions */
|
||||||
/*---------------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------------*/
|
||||||
using NodeIndex = uint8_t;
|
using NodeIndex = uint8_t;
|
||||||
|
using LinkIndex = int32_t;
|
||||||
union NodeAttributeIndex
|
union NodeAttributeIndex
|
||||||
{
|
{
|
||||||
uint16_t Raw;
|
uint16_t Raw;
|
||||||
|
@ -63,7 +64,7 @@ namespace SHADE
|
||||||
};
|
};
|
||||||
union NodeLinkIndex
|
union NodeLinkIndex
|
||||||
{
|
{
|
||||||
uint32_t Raw;
|
LinkIndex Raw;
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
NodeAttributeIndex SourceAttribute;
|
NodeAttributeIndex SourceAttribute;
|
||||||
|
@ -84,17 +85,25 @@ namespace SHADE
|
||||||
|
|
||||||
struct LinkData
|
struct LinkData
|
||||||
{
|
{
|
||||||
|
// Source/Dest Data
|
||||||
std::list<Node>::iterator SourceNode;
|
std::list<Node>::iterator SourceNode;
|
||||||
std::list<Node>::iterator TargetNode;
|
std::list<Node>::iterator TargetNode;
|
||||||
NodeAttributeIndex SourceAttrib;
|
NodeAttributeIndex SourceAttrib;
|
||||||
NodeAttributeIndex DestAttrib;
|
NodeAttributeIndex DestAttrib;
|
||||||
|
// Conditional Data
|
||||||
|
SHAnimationController::Transition::ConditionType Condition;
|
||||||
|
std::string ParamName;
|
||||||
|
SHAnimationController::AnimParam::ValueType ParamThresholdValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using LinkMap = std::unordered_map<LinkIndex, LinkData>;
|
||||||
|
|
||||||
struct AnimControllerData
|
struct AnimControllerData
|
||||||
{
|
{
|
||||||
|
|
||||||
std::list<Node> Nodes;
|
std::list<Node> Nodes;
|
||||||
std::unordered_map<std::string, SHAnimationController::AnimParam::Type> Params;
|
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
|
int NextNodeIndex = 0; // Index to use for newly created nodes
|
||||||
std::unordered_map<NodeIndex, std::list<Node>::iterator> IndexToNodeMap;
|
std::unordered_map<NodeIndex, std::list<Node>::iterator> IndexToNodeMap;
|
||||||
};
|
};
|
||||||
|
@ -103,12 +112,15 @@ namespace SHADE
|
||||||
/* Data Members */
|
/* Data Members */
|
||||||
/*---------------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------------*/
|
||||||
std::optional<AnimControllerData> controllerData;
|
std::optional<AnimControllerData> controllerData;
|
||||||
|
// Persistent Cached Data
|
||||||
|
std::vector<std::string> conditionsList;
|
||||||
|
|
||||||
/*---------------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------------*/
|
||||||
/* Helper Functions */
|
/* Helper Functions */
|
||||||
/*---------------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------------*/
|
||||||
void drawActiveMenuBar();
|
void drawActiveMenuBar();
|
||||||
void drawNodeEditor();
|
void drawNodeEditor();
|
||||||
|
void drawPropertiesPanel();
|
||||||
NodeAttributeIndex getExtraInputAttrib(uint32_t nodeIndex);
|
NodeAttributeIndex getExtraInputAttrib(uint32_t nodeIndex);
|
||||||
NodeAttributeIndex getExtraOutputAttrib(uint32_t nodeIndex);
|
NodeAttributeIndex getExtraOutputAttrib(uint32_t nodeIndex);
|
||||||
void drawInputNode(int id, ImNodesPinShape_ pinShape);
|
void drawInputNode(int id, ImNodesPinShape_ pinShape);
|
||||||
|
@ -118,7 +130,7 @@ namespace SHADE
|
||||||
/* Static Helper Functions */
|
/* Static Helper Functions */
|
||||||
/*---------------------------------------------------------------------------------*/
|
/*---------------------------------------------------------------------------------*/
|
||||||
static std::list<Node>::iterator createNode(AnimControllerData& data);
|
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 AnimControllerData deserialise(const SHAnimationController& controller);
|
||||||
static SHAnimationController serialise(const AnimControllerData& data);
|
static SHAnimationController serialise(const AnimControllerData& data);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue