Implemented Animation Clip asset and animation controller #410
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue