Files
o3de/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/AnimGraphNodeCommands.cpp
T
2021-03-08 14:30:57 -08:00

1569 lines
66 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AnimGraphNodeCommands.h"
#include "AnimGraphConnectionCommands.h"
#include "CommandManager.h"
#include <EMotionFX/CommandSystem/Source/AnimGraphTriggerActionCommands.h>
#include <EMotionFX/Source/ActorInstance.h>
#include <EMotionFX/Source/ActorManager.h>
#include <EMotionFX/Source/AnimGraph.h>
#include <EMotionFX/Source/AnimGraphEntryNode.h>
#include <EMotionFX/Source/AnimGraphExitNode.h>
#include <EMotionFX/Source/AnimGraphInstance.h>
#include <EMotionFX/Source/AnimGraphManager.h>
#include <EMotionFX/Source/AnimGraphMotionCondition.h>
#include <EMotionFX/Source/AnimGraphMotionNode.h>
#include <EMotionFX/Source/AnimGraphNode.h>
#include <EMotionFX/Source/AnimGraphNodeGroup.h>
#include <EMotionFX/Source/AnimGraphObjectFactory.h>
#include <EMotionFX/Source/AnimGraphStateMachine.h>
#include <EMotionFX/Source/BlendTree.h>
#include <EMotionFX/Source/BlendTreeParameterNode.h>
#include <MCore/Source/LogManager.h>
#include <MCore/Source/ReflectionSerializer.h>
namespace CommandSystem
{
AZStd::string GenerateUniqueNodeName(EMotionFX::AnimGraph* animGraph, const AZStd::string& namePrefix, const AZStd::string& typeString, const AZStd::unordered_set<AZStd::string>& nameReserveList = AZStd::unordered_set<AZStd::string>())
{
AZStd::string nameResult;
if (namePrefix.empty())
{
nameResult = animGraph->GenerateNodeName(nameReserveList, typeString.c_str());
}
else
{
nameResult = animGraph->GenerateNodeName(nameReserveList, namePrefix.c_str());
}
// remove the AnimGraph prefix from the node names
AzFramework::StringFunc::Replace(nameResult, "AnimGraph", "", true /* case sensitive */);
// also remove the BlendTree prefix from all other nodes
if (AzFramework::StringFunc::Equal(typeString.c_str(), "BlendTree", false /* no case */) == false)
{
AzFramework::StringFunc::Replace(nameResult, "BlendTree", "", true /* case sensitive */);
}
return nameResult;
}
AZStd::string GenerateUniqueNodeName(EMotionFX::AnimGraph* animGraph, EMotionFX::AnimGraphNode* node, const AZStd::unordered_set<AZStd::string>& nameReserveList)
{
if (node == nullptr)
{
return AZStd::string();
}
AZStd::string namePrefix = node->GetName();
AZStd::string typeString = node->RTTI_GetTypeName();
const char* nameReadPtr = namePrefix.c_str();
size_t newLength = namePrefix.size();
for (size_t i = newLength; i > 0; i--)
{
char currentChar = nameReadPtr[i - 1];
if (isdigit(currentChar) != 0)
{
newLength--;
}
else
{
break;
}
}
if (newLength > 0)
{
namePrefix.resize(newLength);
}
return GenerateUniqueNodeName(animGraph, namePrefix, typeString, nameReserveList);
}
//-------------------------------------------------------------------------------------
// Create a anim graph node
//-------------------------------------------------------------------------------------
// constructor
CommandAnimGraphCreateNode::CommandAnimGraphCreateNode(MCore::Command* orgCommand)
: MCore::Command("AnimGraphCreateNode", orgCommand)
{
}
// destructor
CommandAnimGraphCreateNode::~CommandAnimGraphCreateNode()
{
}
EMotionFX::AnimGraphNodeId CommandAnimGraphCreateNode::GetNodeId(const MCore::CommandLine& parameters)
{
if (parameters.CheckIfHasParameter("nodeId"))
{
const AZStd::string nodeIdString = parameters.GetValue("nodeId", this);
return EMotionFX::AnimGraphNodeId::CreateFromString(nodeIdString);
}
return mNodeId;
}
void CommandAnimGraphCreateNode::DeleteGraphNode(EMotionFX::AnimGraphNode* node)
{
EMotionFX::AnimGraph* animGraph = node->GetAnimGraph();
if (animGraph)
{
animGraph->RemoveAllObjectData(node, true);
}
EMotionFX::AnimGraphNode* parentNode = node->GetParentNode();
if (parentNode)
{
parentNode->RemoveChildNodeByPointer(node);
}
delete node;
}
// execute
bool CommandAnimGraphCreateNode::Execute(const MCore::CommandLine& parameters, AZStd::string& outResult)
{
// get the anim graph to work on
EMotionFX::AnimGraph* animGraph = CommandsGetAnimGraph(parameters, this, outResult);
if (animGraph == nullptr)
{
return false;
}
// store the anim graph id for undo
mAnimGraphID = animGraph->GetID();
// find the graph
EMotionFX::AnimGraphNode* parentNode = nullptr;
AZStd::string parentNodeName;
parameters.GetValue("parentName", "", parentNodeName);
if (!parentNodeName.empty())
{
parentNode = animGraph->RecursiveFindNodeByName(parentNodeName.c_str());
if (!parentNode)
{
outResult = AZStd::string::format("There is no anim graph node with name '%s' inside the selected/active anim graph.", parentNodeName.c_str());
return false;
}
}
// get the type of the node to be created
const AZ::Outcome<AZStd::string> typeString = parameters.GetValueIfExists("type", this);
const AZ::TypeId nodeType = typeString.IsSuccess() ? AZ::TypeId::CreateString(typeString.GetValue().c_str(), typeString.GetValue().size()) : AZ::TypeId::CreateNull();
if (nodeType.IsNull())
{
outResult = AZStd::string::format("Cannot create node of type %s", nodeType.ToString<AZStd::string>().c_str());
return false;
}
if (parentNodeName.empty() && nodeType != azrtti_typeid<EMotionFX::AnimGraphStateMachine>())
{
outResult = "Cannot create node. Root nodes can only be of type AnimGraphStateMachine.";
return false;
}
// get the name check if the name is unique or not
AZStd::string name;
parameters.GetValue("name", "", name);
if (!name.empty())
{
// if there is already a node with the same name
if (animGraph->RecursiveFindNodeByName(name.c_str()))
{
outResult = AZStd::string::format("Failed to create node, as there is already a node with name '%s'", name.c_str());
return false;
}
}
// try to create the node of the given type
EMotionFX::AnimGraphObject* object = EMotionFX::AnimGraphObjectFactory::Create(nodeType, animGraph);
if (!object)
{
outResult = AZStd::string::format("Failed to create node of type '%s'", typeString.GetValue().c_str());
return false;
}
// check if it is really a node
if (!azrtti_istypeof<EMotionFX::AnimGraphNode>(object))
{
outResult = AZStd::string::format("Failed to create node of type '%s' as it is no AnimGraphNode inherited object.", typeString.GetValue().c_str());
return false;
}
// convert the object into a node
EMotionFX::AnimGraphNode* node = static_cast<EMotionFX::AnimGraphNode*>(object);
// store the node id for the callbacks
mNodeId = node->GetId();
if (parameters.CheckIfHasParameter("contents"))
{
AZStd::string contents;
parameters.GetValue("contents", this, &contents);
MCore::ReflectionSerializer::DeserializeMembers(node, contents);
// The deserialize method will deserialize back the old id
node->SetId(mNodeId);
// Verify we have not serialized connections, child nodes and transitions
AZ_Assert(node->GetNumConnections() == 0, "Unexpected serialized connections");
AZ_Assert(node->GetNumChildNodes() == 0, "Unexpected serialized child nodes");
AZ_Assert(azrtti_typeid(node) != azrtti_typeid<EMotionFX::AnimGraphStateMachine>() || static_cast<EMotionFX::AnimGraphStateMachine*>(node)->GetNumTransitions() == 0, "Unexpected serialized transitions");
// After deserialization the trigger actions also need to be initialized.
node->InitTriggerActions();
}
// Force set the node id. Undo of the remove node command calls a create node command which has to force set the node id when reconstructing it.
// Else wise all linked objects like transition conditions will be linked to an invalid node.
if (parameters.CheckIfHasParameter("nodeId"))
{
const AZStd::string nodeIdString = parameters.GetValue("nodeId", this);
const EMotionFX::AnimGraphNodeId nodeId = EMotionFX::AnimGraphNodeId::CreateFromString(nodeIdString);
node->SetId(nodeId);
mNodeId = nodeId;
}
// if the name is not empty, set it
if (!name.empty())
{
if (AzFramework::StringFunc::Equal(name.c_str(), "GENERATE", false /* no case */))
{
AZStd::string namePrefix;
parameters.GetValue("namePrefix", this, &namePrefix);
name = GenerateUniqueNodeName(animGraph, namePrefix, node->RTTI_GetTypeName());
}
node->SetName(name.c_str());
}
// Avoid adding a node with an id that another node in the anim graph already has.
if (animGraph->RecursiveFindNodeById(node->GetId()) != nullptr)
{
outResult = AZStd::string::format("Node with id '%s' and type '%s' cannot be added to anim graph. Node with the given id already exists.", node->GetId().ToString().c_str(), typeString.GetValue().c_str());
DeleteGraphNode(node);
return false;
}
// if its a root node it has to be a state machine
if (parentNode == nullptr)
{
if (azrtti_typeid(node) != azrtti_typeid<EMotionFX::AnimGraphStateMachine>())
{
outResult = AZStd::string::format("Nodes without parents are only allowed to be state machines, cancelling creation!");
DeleteGraphNode(node);
return false;
}
}
else
{
if (azrtti_typeid(parentNode) == azrtti_typeid<EMotionFX::AnimGraphStateMachine>())
{
if (!node->GetCanActAsState())
{
outResult = AZStd::string::format("Node with name '%s' cannot be added to state machine as the node with type '%s' can not act as a state.", name.c_str(), typeString.GetValue().c_str());
DeleteGraphNode(node);
return false;
}
// Handle node types that are only allowed once as a child.
if (node->GetCanHaveOnlyOneInsideParent() && parentNode->HasChildNodeOfType(azrtti_typeid(node)))
{
outResult = AZStd::string::format("Node with name '%s' and type '%s' cannot be added to state machine as a node with the given type already exists. Multiple nodes of this type per state machine are not allowed.", name.c_str(), typeString.GetValue().c_str());
DeleteGraphNode(node);
return false;
}
}
}
// now that the node is created, adjust its position
const int32 xPos = parameters.GetValueAsInt("xPos", this);
const int32 yPos = parameters.GetValueAsInt("yPos", this);
node->SetVisualPos(xPos, yPos);
// set the new value to the enabled flag
if (parameters.CheckIfHasParameter("enabled"))
{
node->SetIsEnabled(parameters.GetValueAsBool("enabled", this));
}
// set the new value to the visualization flag
if (parameters.CheckIfHasParameter("visualize"))
{
node->SetVisualization(parameters.GetValueAsBool("visualize", this));
}
// set the attributes from a string
if (parameters.CheckIfHasParameter("attributesString"))
{
AZStd::string attributesString;
parameters.GetValue("attributesString", this, &attributesString);
MCore::ReflectionSerializer::Deserialize(node, MCore::CommandLine(attributesString));
}
// collapse the node if expected
const bool collapsed = parameters.GetValueAsBool("collapsed", this);
node->SetIsCollapsed(collapsed);
// store the anim graph id for undo
mAnimGraphID = animGraph->GetID();
// check if the parent is valid
if (parentNode)
{
// add the node in the parent
node->SetParentNode(parentNode);
parentNode->AddChildNode(node);
// in case the parent node is a state machine
if (azrtti_typeid(parentNode) == azrtti_typeid<EMotionFX::AnimGraphStateMachine>())
{
// type cast the parent node to a state machine
EMotionFX::AnimGraphStateMachine* stateMachine = static_cast<EMotionFX::AnimGraphStateMachine*>(parentNode);
// in case this is the first state we add to the state machine, default it to the entry state
if (stateMachine->GetNumChildNodes() == 1)
{
stateMachine->SetEntryState(node);
}
}
}
else
{
//animGraph->AddStateMachine( (EMotionFX::AnimGraphStateMachine*)node );
MCORE_ASSERT(false);
MCore::LogError("Cannot add node at root level.");
}
// handle blend tree final node separately
if (parentNode && azrtti_typeid(parentNode) == azrtti_typeid<EMotionFX::BlendTree>())
{
if (node && azrtti_typeid(node) == azrtti_typeid<EMotionFX::BlendTreeFinalNode>())
{
EMotionFX::BlendTree* blendTree = static_cast<EMotionFX::BlendTree*>(parentNode);
blendTree->SetFinalNodeId(node->GetId());
}
}
// save the current dirty flag and tell the anim graph that something got changed
mOldDirtyFlag = animGraph->GetDirtyFlag();
animGraph->SetDirtyFlag(true);
// return the node name
outResult = node->GetName();
// call the post create node event
EMotionFX::GetEventManager().OnCreatedNode(animGraph, node);
node->Reinit();
animGraph->RecursiveInvalidateUniqueDatas();
// init new node for all anim graph instances belonging to it
const uint32 numActorInstances = EMotionFX::GetActorManager().GetNumActorInstances();
for (uint32 i = 0; i < numActorInstances; ++i)
{
EMotionFX::AnimGraphInstance* animGraphInstance = EMotionFX::GetActorManager().GetActorInstance(i)->GetAnimGraphInstance();
if (animGraphInstance && animGraphInstance->GetAnimGraph() == animGraph)
{
// activate the state automatically in all animgraph instances
if (azrtti_typeid(parentNode) == azrtti_typeid<EMotionFX::AnimGraphStateMachine>())
{
if (parentNode->GetNumChildNodes() == 1)
{
EMotionFX::AnimGraphStateMachine* stateMachine = static_cast<EMotionFX::AnimGraphStateMachine*>(parentNode);
stateMachine->SwitchToState(animGraphInstance, node);
}
}
}
}
return true;
}
// undo the command
bool CommandAnimGraphCreateNode::Undo(const MCore::CommandLine& parameters, AZStd::string& outResult)
{
MCORE_UNUSED(parameters);
// get the anim graph
EMotionFX::AnimGraph* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(mAnimGraphID);
if (animGraph == nullptr)
{
outResult = AZStd::string::format("The anim graph with id '%i' does not exist anymore.", mAnimGraphID);
return false;
}
// locate the node
EMotionFX::AnimGraphNode* node = animGraph->RecursiveFindNodeById(mNodeId);
if (node == nullptr)
{
return false;
}
mNodeId.SetInvalid();
const AZStd::string commandString = AZStd::string::format("AnimGraphRemoveNode -animGraphID %i -name \"%s\"", animGraph->GetID(), node->GetName());
if (GetCommandManager()->ExecuteCommandInsideCommand(commandString, outResult) == false)
{
if (outResult.size() > 0)
{
MCore::LogError(outResult.c_str());
}
return false;
}
// set the dirty flag back to the old value
animGraph->SetDirtyFlag(mOldDirtyFlag);
return true;
}
// init the syntax of the command
void CommandAnimGraphCreateNode::InitSyntax()
{
// required
GetSyntax().ReserveParameters(12);
GetSyntax().AddRequiredParameter("type", "The type of the node (UUID).", MCore::CommandSyntax::PARAMTYPE_STRING);
GetSyntax().AddParameter("animGraphID", "The id of the anim graph to work on.", MCore::CommandSyntax::PARAMTYPE_INT, "-1");
GetSyntax().AddParameter("parentName", "The name of the parent node to add it to.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("name", "The name of the node, set to GENERATE to automatically generate a unique name.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("nodeId", "The unique node id of the new node.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("xPos", "The x position of the upper left corner in the visual graph.", MCore::CommandSyntax::PARAMTYPE_INT, "0");
GetSyntax().AddParameter("yPos", "The y position of the upper left corner in the visual graph.", MCore::CommandSyntax::PARAMTYPE_INT, "0");
GetSyntax().AddParameter("collapsed", "The node collapse flag. This is only for the visual representation and does not affect the functionality.", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "false");
GetSyntax().AddParameter("center", "Center the created node around the mouse cursor or not.", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "true");
GetSyntax().AddParameter("namePrefix", "The prefix of the name, when the name is set to GENERATE.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("attributesString", "The node attributes as string.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("contents", "The serialized contents of the parameter (in reflected XML).", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("enabled", "Is the node enabled?", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "true");
GetSyntax().AddParameter("visualize", "Is the node visualized?", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "false");
}
// get the description
const char* CommandAnimGraphCreateNode::GetDescription() const
{
return "This command creates a anim graph node of a given type. It returns the node name if successful.";
}
//-------------------------------------------------------------------------------------
// AnimGraphAdjustNode - adjust node settings
//-------------------------------------------------------------------------------------
// constructor
CommandAnimGraphAdjustNode::CommandAnimGraphAdjustNode(MCore::Command* orgCommand)
: MCore::Command("AnimGraphAdjustNode", orgCommand)
{
mOldPosX = 0;
mOldPosY = 0;
}
// destructor
CommandAnimGraphAdjustNode::~CommandAnimGraphAdjustNode()
{
}
// execute
bool CommandAnimGraphAdjustNode::Execute(const MCore::CommandLine& parameters, AZStd::string& outResult)
{
// get the anim graph to work on
EMotionFX::AnimGraph* animGraph = CommandsGetAnimGraph(parameters, this, outResult);
if (animGraph == nullptr)
{
return false;
}
// store the anim graph id for undo
mAnimGraphID = animGraph->GetID();
// get the name of the node
AZStd::string name;
parameters.GetValue("name", "", name);
// find the node in the anim graph
EMotionFX::AnimGraphNode* node = animGraph->RecursiveFindNodeByName(name.c_str());
if (!node)
{
outResult = AZStd::string::format("Cannot find node with name '%s' in anim graph '%s'", name.c_str(), animGraph->GetFileName());
return false;
}
if (parameters.CheckIfHasParameter("attributesString"))
{
const AZStd::string attributesString = parameters.GetValue("attributesString", this);
MCore::ReflectionSerializer::Deserialize(node, MCore::CommandLine(attributesString));
}
// get the x and y pos
int32 xPos = node->GetVisualPosX();
int32 yPos = node->GetVisualPosY();
mOldPosX = xPos;
mOldPosY = yPos;
// get the new position values
if (parameters.CheckIfHasParameter("xPos"))
{
xPos = parameters.GetValueAsInt("xPos", this);
}
if (parameters.CheckIfHasParameter("yPos"))
{
yPos = parameters.GetValueAsInt("yPos", this);
}
node->SetVisualPos(xPos, yPos);
// set the new name
AZStd::string newName;
parameters.GetValue("newName", this, newName);
if (!newName.empty())
{
// find the node group the node was in before the name change
EMotionFX::AnimGraphNodeGroup* nodeGroup = animGraph->FindNodeGroupForNode(node);
mNodeGroupName.clear();
if (nodeGroup)
{
// remember the node group name for undo
mNodeGroupName = nodeGroup->GetName();
// remove the node from the node group as its id is going to change
nodeGroup->RemoveNodeById(node->GetId());
}
mOldName = node->GetName();
node->SetName(newName.c_str());
// as the id of the node changed after renaming it, we have to readd the node with the new id
if (nodeGroup)
{
nodeGroup->AddNode(node->GetId());
}
// call the post rename node event
EMotionFX::GetEventManager().OnRenamedNode(animGraph, node, mOldName.c_str());
}
// remember and set the new value to the enabled flag
mOldEnabled = node->GetIsEnabled();
if (parameters.CheckIfHasParameter("enabled"))
{
node->SetIsEnabled(parameters.GetValueAsBool("enabled", this));
}
// remember and set the new value to the visualization flag
mOldVisualized = node->GetIsVisualizationEnabled();
if (parameters.CheckIfHasParameter("visualize"))
{
node->SetVisualization(parameters.GetValueAsBool("visualize", this));
}
mNodeId = node->GetId();
// save the current dirty flag and tell the anim graph that something got changed
mOldDirtyFlag = animGraph->GetDirtyFlag();
animGraph->SetDirtyFlag(true);
// only update attributes in case it is wanted
if (parameters.GetValueAsBool("updateAttributes", this))
{
node->Reinit();
animGraph->RecursiveInvalidateUniqueDatas();
}
return true;
}
// undo the command
bool CommandAnimGraphAdjustNode::Undo(const MCore::CommandLine& parameters, AZStd::string& outResult)
{
// get the anim graph
EMotionFX::AnimGraph* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(mAnimGraphID);
if (animGraph == nullptr)
{
outResult = AZStd::string::format("The anim graph with id '%i' does not exist anymore.", mAnimGraphID);
return false;
}
EMotionFX::AnimGraphNode* node = animGraph->RecursiveFindNodeById(mNodeId);
if (node == nullptr)
{
outResult = AZStd::string::format("Cannot find node with ID %s.", mNodeId.ToString().c_str());
return false;
}
// restore the name
if (!mOldName.empty())
{
AZStd::string currentName = node->GetName();
// find the node group the node was in before the name change
EMotionFX::AnimGraphNodeGroup* nodeGroup = animGraph->FindNodeGroupForNode(node);
// remove the node from the node group as its id is going to change
if (nodeGroup)
{
nodeGroup->RemoveNodeById(node->GetId());
}
node->SetName(mOldName.c_str());
// as the id of the node changed after renaming it, we have to readd the node with the new id
if (nodeGroup)
{
nodeGroup->AddNode(node->GetId());
}
// call the post rename node event
EMotionFX::GetEventManager().OnRenamedNode(animGraph, node, node->GetName());
}
mNodeId = node->GetId();
node->SetVisualPos(mOldPosX, mOldPosY);
// set the old values to the enabled flag and the visualization flag
node->SetIsEnabled(mOldEnabled);
node->SetVisualization(mOldVisualized);
// do only for parameter nodes
if (azrtti_typeid(node) == azrtti_typeid<EMotionFX::BlendTreeParameterNode>() && parameters.CheckIfHasParameter("parameterMask"))
{
// type cast to a parameter node
EMotionFX::BlendTreeParameterNode* parameterNode = static_cast<EMotionFX::BlendTreeParameterNode*>(node);
// get the parameter mask attribute and update the mask
parameterNode->SetParameters(mOldParameterMask);
}
// set the dirty flag back to the old value
animGraph->SetDirtyFlag(mOldDirtyFlag);
node->Reinit();
animGraph->RecursiveInvalidateUniqueDatas();
return true;
}
// init the syntax of the command
void CommandAnimGraphAdjustNode::InitSyntax()
{
// required
GetSyntax().ReserveParameters(8);
GetSyntax().AddRequiredParameter("animGraphID", "The id of the anim graph to work on.", MCore::CommandSyntax::PARAMTYPE_INT);
GetSyntax().AddRequiredParameter("name", "The name of the node to modify.", MCore::CommandSyntax::PARAMTYPE_STRING);
GetSyntax().AddParameter("newName", "The new name of the node.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("xPos", "The new x position of the upper left corner in the visual graph.", MCore::CommandSyntax::PARAMTYPE_INT, "0");
GetSyntax().AddParameter("yPos", "The new y position of the upper left corner in the visual graph.", MCore::CommandSyntax::PARAMTYPE_INT, "0");
GetSyntax().AddParameter("enabled", "Is the node enabled?", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "true");
GetSyntax().AddParameter("visualize", "Is the node visualized?", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "false");
GetSyntax().AddParameter("updateAttributes", "Update attributes afterwards?", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "true");
GetSyntax().AddParameter("attributesString", "The node attributes as string.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
}
// get the description
const char* CommandAnimGraphAdjustNode::GetDescription() const
{
return "This command adjust properties of a given anim graph node.";
}
//-------------------------------------------------------------------------------------
// Remove a anim graph node
//-------------------------------------------------------------------------------------
// constructor
CommandAnimGraphRemoveNode::CommandAnimGraphRemoveNode(MCore::Command* orgCommand)
: MCore::Command("AnimGraphRemoveNode", orgCommand)
{
mIsEntryNode = false;
}
// destructor
CommandAnimGraphRemoveNode::~CommandAnimGraphRemoveNode()
{
}
// execute
bool CommandAnimGraphRemoveNode::Execute(const MCore::CommandLine& parameters, AZStd::string& outResult)
{
// get the anim graph to work on
EMotionFX::AnimGraph* animGraph = CommandsGetAnimGraph(parameters, this, outResult);
if (animGraph == nullptr)
{
return false;
}
// store the anim graph id for undo
mAnimGraphID = animGraph->GetID();
// find the emfx node
AZStd::string name;
parameters.GetValue("name", "", name);
EMotionFX::AnimGraphNode* emfxNode = animGraph->RecursiveFindNodeByName(name.c_str());
if (emfxNode == nullptr)
{
outResult = AZStd::string::format("There is no node with the name '%s'", name.c_str());
return false;
}
mType = azrtti_typeid(emfxNode);
mName = emfxNode->GetName();
mPosX = emfxNode->GetVisualPosX();
mPosY = emfxNode->GetVisualPosY();
mCollapsed = emfxNode->GetIsCollapsed();
mOldContents = MCore::ReflectionSerializer::SerializeMembersExcept(emfxNode, { "childNodes", "connections", "transitions" }).GetValue();
mNodeId = emfxNode->GetId();
// remember the node group for the node for undo
mNodeGroupName.clear();
EMotionFX::AnimGraphNodeGroup* nodeGroup = animGraph->FindNodeGroupForNode(emfxNode);
if (nodeGroup)
{
mNodeGroupName = nodeGroup->GetName();
}
// get the parent node
EMotionFX::AnimGraphNode* parentNode = emfxNode->GetParentNode();
if (parentNode)
{
if (azrtti_typeid(parentNode) == azrtti_typeid<EMotionFX::AnimGraphStateMachine>())
{
EMotionFX::AnimGraphStateMachine* stateMachine = static_cast<EMotionFX::AnimGraphStateMachine*>(parentNode);
if (stateMachine->GetEntryState() == emfxNode)
{
mIsEntryNode = true;
// Find a new entry node if we can
//--------------------------
// Find alternative entry state.
EMotionFX::AnimGraphNode* newEntryState = nullptr;
uint32 numStates = stateMachine->GetNumChildNodes();
for (uint32 s = 0; s < numStates; ++s)
{
EMotionFX::AnimGraphNode* childNode = stateMachine->GetChildNode(s);
if (childNode != emfxNode)
{
newEntryState = childNode;
break;
}
}
// Check if we've found a new possible entry state.
if (newEntryState)
{
AZStd::string commandString = AZStd::string::format("AnimGraphSetEntryState -animGraphID %i -entryNodeName \"%s\"", animGraph->GetID(), newEntryState->GetName());
if (!GetCommandManager()->ExecuteCommandInsideCommand(commandString.c_str(), outResult))
{
AZ_Error("EMotionFX", false, outResult.c_str());
}
}
}
}
mParentName = parentNode->GetName();
mParentNodeId = parentNode->GetId();
// call the pre remove node event
EMotionFX::GetEventManager().OnRemoveNode(animGraph, emfxNode);
// remove all unique datas for the node
animGraph->RemoveAllObjectData(emfxNode, true);
// remove the actual node
parentNode->RemoveChildNodeByPointer(emfxNode);
}
else
{
mParentNodeId.SetInvalid();
mParentName.clear();
MCore::LogError("Cannot remove root state machine.");
MCORE_ASSERT(false);
return false;
}
// save the current dirty flag and tell the anim graph that something got changed
mOldDirtyFlag = animGraph->GetDirtyFlag();
animGraph->SetDirtyFlag(true);
animGraph->RecursiveInvalidateUniqueDatas();
return true;
}
// undo the command
bool CommandAnimGraphRemoveNode::Undo(const MCore::CommandLine& parameters, AZStd::string& outResult)
{
MCORE_UNUSED(parameters);
EMotionFX::AnimGraph* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(mAnimGraphID);
if (!animGraph)
{
outResult = AZStd::string::format("The anim graph with id '%i' does not exist anymore.", mAnimGraphID);
return false;
}
// create the node again
MCore::CommandGroup group("Recreating node");
AZStd::string commandString;
if (!mParentName.empty())
{
commandString = AZStd::string::format("AnimGraphCreateNode -animGraphID %i -type \"%s\" -parentName \"%s\" -name \"%s\" -nodeId \"%s\" -xPos %d -yPos %d -collapsed %s -center false -contents {%s}",
animGraph->GetID(),
mType.ToString<AZStd::string>().c_str(),
mParentName.c_str(),
mName.c_str(),
mNodeId.ToString().c_str(),
mPosX,
mPosY,
AZStd::to_string(mCollapsed).c_str(),
mOldContents.c_str());
group.AddCommandString(commandString);
if (mIsEntryNode)
{
commandString = AZStd::string::format("AnimGraphSetEntryState -animGraphID %i -entryNodeName \"%s\"", animGraph->GetID(), mName.c_str());
group.AddCommandString(commandString);
}
}
else
{
commandString = AZStd::string::format("AnimGraphCreateNode -animGraphID %i -type \"%s\" -name \"%s\" -nodeId \"%s\" -xPos %d -yPos %d -collapsed %s -center false -contents {%s}",
animGraph->GetID(),
mType.ToString<AZStd::string>().c_str(),
mName.c_str(),
mNodeId.ToString().c_str(),
mPosX,
mPosY,
AZStd::to_string(mCollapsed).c_str(),
mOldContents.c_str());
group.AddCommandString(commandString);
}
if (!GetCommandManager()->ExecuteCommandGroupInsideCommand(group, outResult))
{
if (outResult.size() > 0)
{
MCore::LogError(outResult.c_str());
}
return false;
}
// add it to the old node group if it was assigned to one before
if (!mNodeGroupName.empty())
{
commandString = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %i -name \"%s\" -nodeNames \"%s\" -nodeAction \"add\"", animGraph->GetID(), mNodeGroupName.c_str(), mName.c_str());
if (GetCommandManager()->ExecuteCommandInsideCommand(commandString.c_str(), outResult) == false)
{
if (outResult.size() > 0)
{
MCore::LogError(outResult.c_str());
}
return false;
}
}
// set the dirty flag back to the old value
animGraph->SetDirtyFlag(mOldDirtyFlag);
return true;
}
// init the syntax of the command
void CommandAnimGraphRemoveNode::InitSyntax()
{
// required
GetSyntax().ReserveParameters(2);
GetSyntax().AddRequiredParameter("animGraphID", "The id of the anim graph to work on.", MCore::CommandSyntax::PARAMTYPE_INT);
GetSyntax().AddRequiredParameter("name", "The name of the node to remove.", MCore::CommandSyntax::PARAMTYPE_STRING);
}
// get the description
const char* CommandAnimGraphRemoveNode::GetDescription() const
{
return "This command removes a anim graph nodewith a given name.";
}
//-------------------------------------------------------------------------------------
// Set the entry state of a state machine
//-------------------------------------------------------------------------------------
// constructor
CommandAnimGraphSetEntryState::CommandAnimGraphSetEntryState(MCore::Command* orgCommand)
: MCore::Command("AnimGraphSetEntryState", orgCommand)
{
}
// destructor
CommandAnimGraphSetEntryState::~CommandAnimGraphSetEntryState()
{
}
// execute
bool CommandAnimGraphSetEntryState::Execute(const MCore::CommandLine& parameters, AZStd::string& outResult)
{
// get the anim graph to work on
EMotionFX::AnimGraph* animGraph = CommandsGetAnimGraph(parameters, this, outResult);
if (animGraph == nullptr)
{
return false;
}
// store the anim graph id for undo
mAnimGraphID = animGraph->GetID();
AZStd::string entryNodeName;
parameters.GetValue("entryNodeName", this, entryNodeName);
// find the entry anim graph node
EMotionFX::AnimGraphNode* entryNode = animGraph->RecursiveFindNodeByName(entryNodeName.c_str());
if (!entryNode)
{
outResult = AZStd::string::format("There is no entry node with the name '%s'", entryNodeName.c_str());
return false;
}
// check if the parent node is a state machine
EMotionFX::AnimGraphNode* stateMachineNode = entryNode->GetParentNode();
if (stateMachineNode == nullptr || azrtti_typeid(stateMachineNode) != azrtti_typeid<EMotionFX::AnimGraphStateMachine>())
{
outResult = AZStd::string::format("Cannot set entry node '%s'. Parent node is not a state machine or not valid at all.", entryNodeName.c_str());
return false;
}
// Check if the node can be set as the entry node
if (!entryNode->GetCanBeEntryNode())
{
outResult = AZStd::string::format("Cannot set entry node '%s'. This type of node cannot be set as an entry node.", entryNodeName.c_str());
return false;
}
// get the parent state machine
EMotionFX::AnimGraphStateMachine* stateMachine = (EMotionFX::AnimGraphStateMachine*)stateMachineNode;
// store the id of the old entry node
EMotionFX::AnimGraphNode* oldEntryNode = stateMachine->GetEntryState();
if (oldEntryNode)
{
mOldEntryStateNodeId = oldEntryNode->GetId();
}
else
{
mOldEntryStateNodeId.SetInvalid();
}
// store the id of the state machine
mOldStateMachineNodeId = stateMachineNode->GetId();
// set the new entry state for the state machine
stateMachine->SetEntryState(entryNode);
// save the current dirty flag and tell the anim graph that something got changed
mOldDirtyFlag = animGraph->GetDirtyFlag();
animGraph->SetDirtyFlag(true);
stateMachine->Reinit();
animGraph->RecursiveInvalidateUniqueDatas();
return true;
}
// undo the command
bool CommandAnimGraphSetEntryState::Undo(const MCore::CommandLine& parameters, AZStd::string& outResult)
{
MCORE_UNUSED(parameters);
// get the anim graph
EMotionFX::AnimGraph* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(mAnimGraphID);
if (animGraph == nullptr)
{
outResult = AZStd::string::format("The anim graph with id '%i' does not exist anymore.", mAnimGraphID);
return false;
}
// get the state machine
EMotionFX::AnimGraphNode* stateMachineNode = animGraph->RecursiveFindNodeById(mOldStateMachineNodeId);
if (stateMachineNode == nullptr || azrtti_typeid(stateMachineNode) != azrtti_typeid<EMotionFX::AnimGraphStateMachine>())
{
outResult = "Cannot undo set entry node. Parent node is not a state machine or not valid at all.";
return false;
}
// get the parent state machine
EMotionFX::AnimGraphStateMachine* stateMachine = (EMotionFX::AnimGraphStateMachine*)stateMachineNode;
// find the entry anim graph node
if (mOldEntryStateNodeId.IsValid())
{
EMotionFX::AnimGraphNode* entryNode = animGraph->RecursiveFindNodeById(mOldEntryStateNodeId);
if (!entryNode)
{
outResult = "Cannot undo set entry node. Old entry node cannot be found.";
return false;
}
// set the old entry state for the state machine
stateMachine->SetEntryState(entryNode);
}
else
{
// set the old entry state for the state machine
stateMachine->SetEntryState(nullptr);
}
// set the dirty flag back to the old value
animGraph->SetDirtyFlag(mOldDirtyFlag);
stateMachine->Reinit();
animGraph->RecursiveInvalidateUniqueDatas();
return true;
}
// init the syntax of the command
void CommandAnimGraphSetEntryState::InitSyntax()
{
GetSyntax().ReserveParameters(2);
GetSyntax().AddRequiredParameter("entryNodeName", "The name of the new entry node.", MCore::CommandSyntax::PARAMTYPE_STRING);
GetSyntax().AddParameter("animGraphID", "The id of the anim graph to work on.", MCore::CommandSyntax::PARAMTYPE_INT, "-1");
}
// get the description
const char* CommandAnimGraphSetEntryState::GetDescription() const
{
return "This command sets the entry state of a state machine.";
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void CreateAnimGraphNode(MCore::CommandGroup* commandGroup, EMotionFX::AnimGraph* animGraph, const AZ::TypeId& type, const AZStd::string& namePrefix, EMotionFX::AnimGraphNode* parentNode, int32 offsetX, int32 offsetY, const AZStd::string& serializedContents)
{
AZStd::string commandString;
MCore::CommandGroup internalGroup;
// Add the newly generated commands to the commandGroup parameter in case it is valid, add it to the internal group and execute it otherwise.
bool executeGroup = false;
if (!commandGroup)
{
executeGroup = true;
commandGroup = &internalGroup;
}
// if we want to add a blendtree, we also should automatically add a final node
if (type == azrtti_typeid<EMotionFX::BlendTree>())
{
MCORE_ASSERT(parentNode);
commandGroup->SetGroupName("Create blend tree");
commandString = AZStd::string::format("AnimGraphCreateNode -animGraphID %i -type \"%s\" -parentName \"%s\" -xPos %d -yPos %d -name GENERATE -namePrefix \"%s\"",
animGraph->GetID(),
type.ToString<AZStd::string>().c_str(),
parentNode->GetName(),
offsetX,
offsetY,
namePrefix.c_str());
if (!serializedContents.empty())
{
commandString += AZStd::string::format(" -contents {%s} ", serializedContents.c_str());
}
commandGroup->AddCommandString(commandString);
// auto create the final node
commandString = AZStd::string::format("AnimGraphCreateNode -animGraphID %i -type \"%s\" -parentName \"%%LASTRESULT%%\" -xPos %d -yPos %d -name GENERATE -namePrefix \"FinalNode\"",
animGraph->GetID(),
azrtti_typeid<EMotionFX::BlendTreeFinalNode>().ToString<AZStd::string>().c_str(),
0,
0);
commandGroup->AddCommandString(commandString);
}
// if we want to add a state machine, we also should automatically add an exit node
else if (type == azrtti_typeid<EMotionFX::AnimGraphStateMachine>())
{
MCORE_ASSERT(parentNode);
commandGroup->SetGroupName("Create child state machine");
commandString = AZStd::string::format("AnimGraphCreateNode -animGraphID %i -type \"%s\" -parentName \"%s\" -xPos %d -yPos %d -name GENERATE -namePrefix \"%s\"",
animGraph->GetID(),
type.ToString<AZStd::string>().c_str(),
parentNode->GetName(),
offsetX,
offsetY,
namePrefix.c_str());
if (!serializedContents.empty())
{
commandString += AZStd::string::format(" -contents {%s} ", serializedContents.c_str());
}
commandGroup->AddCommandString(commandString);
// auto create an exit node in case we're not creating a state machine inside a blend tree
if (azrtti_typeid(parentNode) != azrtti_typeid<EMotionFX::BlendTree>())
{
commandString = AZStd::string::format("AnimGraphCreateNode -animGraphID %i -type \"%s\" -parentName \"%%LASTRESULT%%\" -xPos %d -yPos %d -name GENERATE -namePrefix \"EntryNode\"",
animGraph->GetID(),
azrtti_typeid<EMotionFX::AnimGraphEntryNode>().ToString<AZStd::string>().c_str(),
-200,
0);
commandGroup->AddCommandString(commandString);
commandString = AZStd::string::format("AnimGraphCreateNode -animGraphID %i -type \"%s\" -parentName \"%%LASTRESULT2%%\" -xPos %d -yPos %d -name GENERATE -namePrefix \"ExitNode\"",
animGraph->GetID(),
azrtti_typeid<EMotionFX::AnimGraphExitNode>().ToString<AZStd::string>().c_str(),
200,
0);
commandGroup->AddCommandString(commandString);
}
}
else
{
commandGroup->SetGroupName(AZStd::string::format("Create %s node", namePrefix.c_str()));
if (parentNode)
{
commandString = AZStd::string::format("AnimGraphCreateNode -animGraphID %i -type \"%s\" -parentName \"%s\" -xPos %d -yPos %d -name GENERATE -namePrefix \"%s\"",
animGraph->GetID(),
type.ToString<AZStd::string>().c_str(),
parentNode->GetName(),
offsetX,
offsetY,
namePrefix.c_str());
}
else
{
commandString = AZStd::string::format("AnimGraphCreateNode -animGraphID %i -type \"%s\" -xPos %d -yPos %d -name GENERATE -namePrefix \"%s\"",
animGraph->GetID(),
type.ToString<AZStd::string>().c_str(),
offsetX,
offsetY,
namePrefix.c_str());
}
if (!serializedContents.empty())
{
commandString += AZStd::string::format(" -contents {%s} ", serializedContents.c_str());
}
commandGroup->AddCommandString(commandString);
}
// Execute the command group in case the command group parameter was not set.
if (executeGroup &&
!commandGroup->IsEmpty())
{
AZStd::string result;
if (!GetCommandManager()->ExecuteCommandGroup(*commandGroup, result))
{
AZ_Error("EMotionFX", false, result.c_str());
}
}
}
void DeleteNode(MCore::CommandGroup* commandGroup, EMotionFX::AnimGraph* animGraph, EMotionFX::AnimGraphNode* node, AZStd::vector<EMotionFX::AnimGraphNode*>& nodeList, AZStd::vector<EMotionFX::BlendTreeConnection*>& connectionList, AZStd::vector<EMotionFX::AnimGraphStateTransition*>& transitionList, bool recursive, bool firstRootIteration = true, bool autoChangeEntryStates = true)
{
MCORE_UNUSED(recursive);
MCORE_UNUSED(autoChangeEntryStates);
if (!node)
{
return;
}
// Skip directly if the node is already in the list.
if (AZStd::find(nodeList.begin(), nodeList.end(), node) != nodeList.end())
{
return;
}
// Only delete nodes that are also deletable, final nodes e.g. can't be cut and deleted.
if (firstRootIteration && !node->GetIsDeletable())
{
return;
}
// Check if the last instance is not deletable while all others are.
if (firstRootIteration && !node->GetIsLastInstanceDeletable())
{
EMotionFX::AnimGraphNode* parentNode = node->GetParentNode();
if (parentNode)
{
// Gather the number of nodes with the same type as the one we're trying to remove.
MCore::Array<EMotionFX::AnimGraphNode*> outNodes;
const AZ::TypeId nodeType = azrtti_typeid(node);
parentNode->CollectChildNodesOfType(nodeType, &outNodes);
const uint32 numTypeNodes = outNodes.GetLength();
// Gather the number of already removed nodes with the same type as the one we're trying to remove.
const size_t numTotalDeletedNodes = nodeList.size();
uint32 numTypeDeletedNodes = 0;
for (size_t i = 0; i < numTotalDeletedNodes; ++i)
{
// Check if the nodes have the same parent, meaning they are in the same graph plus check if they have the same type
// if that both is the same we can increase the number of deleted nodes for the graph where the current node is in.
if (nodeList[i]->GetParentNode() == parentNode && azrtti_typeid(nodeList[i]) == nodeType)
{
numTypeDeletedNodes++;
}
}
// In case there the number of nodes with the same type as the given node is bigger than the number of already removed nodes + 1 means there is
// only one node left with the given type return directly without deleting the node as we're not allowed to remove the last instance of the node.
if (numTypeDeletedNodes + 1 >= numTypeNodes)
{
return;
}
}
}
/////////////////////////
// 1. Delete all connections and transitions that are connected to the node.
// Delete all incoming and outgoing connections from current node.
DeleteNodeConnections(commandGroup, node, node->GetParentNode(), connectionList, true);
DeleteStateTransitions(commandGroup, node, node->GetParentNode(), transitionList, true);
/////////////////////////
// 2. Delete all child nodes recursively before deleting the node.
// Get the number of child nodes, iterate through them and recursively call the function.
const uint32 numChildNodes = node->GetNumChildNodes();
for (uint32 i = 0; i < numChildNodes; ++i)
{
EMotionFX::AnimGraphNode* childNode = node->GetChildNode(i);
DeleteNode(commandGroup, animGraph, childNode, nodeList, connectionList, transitionList, true, false, false);
}
/////////////////////////
// 3. Delete the node.
// Remove state actions back to front.
const size_t numActions = node->GetTriggerActionSetup().GetNumActions();
for (size_t i = 0; i < numActions; ++i)
{
const size_t actionIndex = numActions - i - 1;
RemoveStateAction(node, actionIndex, commandGroup);
}
const AZStd::string commandString = AZStd::string::format("AnimGraphRemoveNode -animGraphID %i -name \"%s\"", node->GetAnimGraph()->GetID(), node->GetName());
commandGroup->AddCommandString(commandString);
nodeList.push_back(node);
}
void DeleteNodes(MCore::CommandGroup* commandGroup, EMotionFX::AnimGraph* animGraph, const AZStd::vector<AZStd::string>& nodeNames, AZStd::vector<EMotionFX::AnimGraphNode*>& nodeList, AZStd::vector<EMotionFX::BlendTreeConnection*>& connectionList, AZStd::vector<EMotionFX::AnimGraphStateTransition*>& transitionList, bool autoChangeEntryStates)
{
const size_t numNodeNames = nodeNames.size();
for (size_t i = 0; i < numNodeNames; ++i)
{
EMotionFX::AnimGraphNode* node = animGraph->RecursiveFindNodeByName(nodeNames[i].c_str());
// Add the delete node commands to the command group.
DeleteNode(commandGroup, animGraph, node, nodeList, connectionList, transitionList, true, true, autoChangeEntryStates);
}
}
void DeleteNodes(EMotionFX::AnimGraph* animGraph, const AZStd::vector<AZStd::string>& nodeNames)
{
MCore::CommandGroup commandGroup("Delete anim graph nodes");
AZStd::vector<EMotionFX::BlendTreeConnection*> connectionList;
AZStd::vector<EMotionFX::AnimGraphStateTransition*> transitionList;
AZStd::vector<EMotionFX::AnimGraphNode*> nodeList;
DeleteNodes(&commandGroup, animGraph, nodeNames, nodeList, connectionList, transitionList);
AZStd::string result;
if (!GetCommandManager()->ExecuteCommandGroup(commandGroup, result))
{
AZ_Error("EMotionFX", false, result.c_str());
}
}
void DeleteNodes(MCore::CommandGroup* commandGroup, EMotionFX::AnimGraph* animGraph, const AZStd::vector<EMotionFX::AnimGraphNode*>& nodes, bool autoChangeEntryStates)
{
AZStd::vector<EMotionFX::BlendTreeConnection*> connectionList;
AZStd::vector<EMotionFX::AnimGraphStateTransition*> transitionList;
AZStd::vector<EMotionFX::AnimGraphNode*> nodeList;
for (EMotionFX::AnimGraphNode* node : nodes)
{
// Add the delete node commands to the command group.
DeleteNode(commandGroup, animGraph, node, nodeList, connectionList, transitionList, true, true, autoChangeEntryStates);
}
}
void CopyAnimGraphNodeCommand(MCore::CommandGroup* commandGroup, EMotionFX::AnimGraph* targetAnimGraph, EMotionFX::AnimGraphNode* targetNode, EMotionFX::AnimGraphNode* node,
bool cutMode, AZStd::unordered_map<AZ::u64, AZ::u64>& convertedIds, AnimGraphCopyPasteData& copyPasteData, AZStd::unordered_set<AZStd::string>& generatedNames)
{
if (!node)
{
return;
}
// Construct the parent name
const AZStd::string parentName = copyPasteData.GetNewNodeName(targetNode, cutMode);
EMotionFX::AnimGraphNodeId nodeId;
AZStd::string nodeName = node->GetNameString();
if (cutMode)
{
nodeId = node->GetId();
}
else
{
// Create new node id and name
nodeId = EMotionFX::AnimGraphNodeId::Create();
convertedIds.emplace(node->GetId(), nodeId);
nodeName = GenerateUniqueNodeName(targetAnimGraph, node, generatedNames);
generatedNames.emplace(nodeName);
}
copyPasteData.m_newNamesByCopiedNodes.emplace(node, nodeName);
AZStd::string commandString = AZStd::string::format("AnimGraphCreateNode -animGraphID %d -type \"%s\" -parentName \"%s\" -xPos %i -yPos %i -name \"%s\" -collapsed %s -enabled %s -visualize %s -nodeId %s",
targetAnimGraph->GetID(),
azrtti_typeid(node).ToString<AZStd::string>().c_str(),
parentName.c_str(),
node->GetVisualPosX(),
node->GetVisualPosY(),
nodeName.c_str(),
AZStd::to_string(node->GetIsCollapsed()).c_str(),
AZStd::to_string(node->GetIsEnabled()).c_str(),
AZStd::to_string(node->GetIsVisualizationEnabled()).c_str(),
nodeId.ToString().c_str());
// Don't put that into the format as the attribute string can become pretty big strings.
commandString += " -contents {";
commandString += MCore::ReflectionSerializer::SerializeMembersExcept(node, { "childNodes", "connections", "transitions" }).GetValue();
commandString += "}";
commandGroup->AddCommandString(commandString);
if (!cutMode)
{
AZStd::string attributesString;
node->GetAttributeStringForAffectedNodeIds(convertedIds, attributesString);
if (!attributesString.empty())
{
// need to convert
commandString = AZStd::string::format("AnimGraphAdjustNode -animGraphID %d -name \"%s\" -attributesString {%s}",
targetAnimGraph->GetID(),
nodeName.c_str(),
attributesString.c_str());
commandGroup->AddCommandString(commandString);
}
}
// Check if the given node is part of a node group.
EMotionFX::AnimGraphNodeGroup* nodeGroup = node->GetAnimGraph()->FindNodeGroupForNode(node);
if (nodeGroup && !cutMode)
{
commandString = AZStd::string::format("AnimGraphAdjustNodeGroup -animGraphID %d -name \"%s\" -nodeNames \"%s\" -nodeAction \"add\"",
targetAnimGraph->GetID(),
nodeGroup->GetName(),
nodeName.c_str());
commandGroup->AddCommandString(commandString);
}
// Recurse through the child nodes.
const uint32 numChildNodes = node->GetNumChildNodes();
for (uint32 i = 0; i < numChildNodes; ++i)
{
EMotionFX::AnimGraphNode* childNode = node->GetChildNode(i);
CopyAnimGraphNodeCommand(commandGroup, targetAnimGraph, node, childNode,
cutMode, convertedIds, copyPasteData, generatedNames);
}
}
void CopyAnimGraphConnectionsCommand(MCore::CommandGroup* commandGroup, EMotionFX::AnimGraph* targetAnimGraph, EMotionFX::AnimGraphNode* node,
bool cutMode, AZStd::unordered_map<AZ::u64, AZ::u64>& convertedIds, AnimGraphCopyPasteData& copyPasteData, AZStd::unordered_set<AZStd::string>& generatedNames,
bool ignoreTopLevelConnections)
{
if (!node)
{
return;
}
// Recurse through the child nodes.
const uint32 numChildNodes = node->GetNumChildNodes();
for (uint32 i = 0; i < numChildNodes; ++i)
{
EMotionFX::AnimGraphNode* childNode = node->GetChildNode(i);
CopyAnimGraphConnectionsCommand(commandGroup, targetAnimGraph, childNode,
cutMode, convertedIds, copyPasteData, generatedNames,
false);
}
if (!ignoreTopLevelConnections)
{
if (azrtti_typeid(node) == azrtti_typeid<EMotionFX::AnimGraphStateMachine>())
{
EMotionFX::AnimGraphStateMachine* stateMachine = static_cast<EMotionFX::AnimGraphStateMachine*>(node);
EMotionFX::AnimGraphNode* entryState = stateMachine->GetEntryState();
if (entryState)
{
const AZStd::string entryStateName = copyPasteData.GetNewNodeName(entryState, cutMode);
const AZStd::string commandString = AZStd::string::format("AnimGraphSetEntryState -animGraphID %d -entryNodeName \"%s\"", targetAnimGraph->GetID(), entryStateName.c_str());
commandGroup->AddCommandString(commandString);
}
const size_t numTransitions = stateMachine->GetNumTransitions();
for (size_t i = 0; i < numTransitions; ++i)
{
CopyStateTransition(commandGroup, targetAnimGraph, stateMachine->GetTransition(i),
cutMode, convertedIds, copyPasteData);
}
}
else
{
const uint32 numConnections = node->GetNumConnections();
for (uint32 i = 0; i < numConnections; ++i)
{
EMotionFX::BlendTreeConnection* connection = node->GetConnection(i);
CopyBlendTreeConnection(commandGroup, targetAnimGraph, node, connection,
cutMode, convertedIds, copyPasteData);
}
}
}
}
void ConstructCopyAnimGraphNodesCommandGroup(MCore::CommandGroup* commandGroup, EMotionFX::AnimGraphNode* targetNode, AZStd::vector<EMotionFX::AnimGraphNode*>& nodesToCopy, int32 posX, int32 posY, bool cutMode, AnimGraphCopyPasteData& copyPasteData, bool ignoreTopLevelConnections)
{
if (nodesToCopy.empty())
{
return;
}
// Remove all nodes that are child nodes of other selected nodes.
for (size_t i = 0; i < nodesToCopy.size();)
{
EMotionFX::AnimGraphNode* node = nodesToCopy[i];
bool removeNode = false;
for (size_t j = 0; j < nodesToCopy.size(); ++j)
{
if (node != nodesToCopy[j] && node->RecursiveIsParentNode(nodesToCopy[j]))
{
removeNode = true;
break;
}
}
if (removeNode)
{
nodesToCopy.erase(nodesToCopy.begin() + i);
}
else
{
i++;
}
}
// In case we are in cut and paste mode and delete the cut nodes.
if (cutMode)
{
DeleteNodes(commandGroup, nodesToCopy.front()->GetAnimGraph(), nodesToCopy, false);
}
AZStd::unordered_map<AZ::u64, AZ::u64> convertedIds;
AZStd::unordered_set<AZStd::string> generatedNames;
for (EMotionFX::AnimGraphNode* node : nodesToCopy)
{
if (cutMode || !node->GetCanHaveOnlyOneInsideParent())
{
CopyAnimGraphNodeCommand(commandGroup, targetNode->GetAnimGraph(), targetNode, node,
cutMode, convertedIds, copyPasteData, generatedNames);
}
}
// Collect transitions between the nodes to copy (wildcards to a target transition that is being copied or transitions
// where both the source and destination are being copied)
AZStd::unordered_set<EMotionFX::AnimGraphStateMachine*> parentStateMachines;
for (EMotionFX::AnimGraphNode* node : nodesToCopy)
{
CopyAnimGraphConnectionsCommand(commandGroup, targetNode->GetAnimGraph(), node,
cutMode, convertedIds, copyPasteData, generatedNames,
ignoreTopLevelConnections);
// Collect parent state machines for cut/copied nodes to avoid copying transitions multiple times.
// The ownership for blend tree connections is defined differently than for state machines. State machines own the transitions
// while the blend tree does not own any connections. The nodes within the blend tree hold their incoming connections.
EMotionFX::AnimGraphNode* parentNode = node->GetParentNode();
if (parentNode &&
azrtti_typeid(parentNode) == azrtti_typeid<EMotionFX::AnimGraphStateMachine>() &&
AZStd::find(nodesToCopy.begin(), nodesToCopy.end(), parentNode) == nodesToCopy.end())
{
EMotionFX::AnimGraphStateMachine* parentSM = static_cast<EMotionFX::AnimGraphStateMachine*>(parentNode);
if (parentStateMachines.find(parentSM) == parentStateMachines.end())
{
parentStateMachines.emplace(parentSM);
}
}
}
// Copy state transitions
if (!ignoreTopLevelConnections)
{
EMotionFX::AnimGraphStateMachine* targetStateMachine = azdynamic_cast<EMotionFX::AnimGraphStateMachine*>(targetNode);
if (targetStateMachine)
{
for (EMotionFX::AnimGraphStateMachine* stateMachine : parentStateMachines)
{
const size_t numTransitions = stateMachine->GetNumTransitions();
for (size_t t = 0; t < numTransitions; ++t)
{
CopyStateTransition(commandGroup, targetStateMachine->GetAnimGraph(), stateMachine->GetTransition(t), cutMode, convertedIds, copyPasteData);
}
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
// PHASE 1: Iterate over the top level copy&paste nodes and calculate the mid point of them.
///////////////////////////////////////////////////////////////////////////////////////////////////////
// Variables to sum up the node positions to later calculate the middle point of the copied nodes.
// We only need to fix the top-level nodes
int32 middlePosX = 0;
int32 middlePosY = 0;
for (EMotionFX::AnimGraphNode* node : nodesToCopy)
{
middlePosX += node->GetVisualPosX();
middlePosY += node->GetVisualPosY();
}
middlePosX = static_cast<int32>(MCore::Math::SignOfFloat((float)middlePosX) * (MCore::Math::Abs((float)middlePosX) / (float)nodesToCopy.size()));
middlePosY = static_cast<int32>(MCore::Math::SignOfFloat((float)middlePosY) * (MCore::Math::Abs((float)middlePosY) / (float)nodesToCopy.size()));
///////////////////////////////////////////////////////////////////////////////////////////////////////
// PHASE 2: Adjust attributes to new position
///////////////////////////////////////////////////////////////////////////////////////////////////////
for (EMotionFX::AnimGraphNode* node : nodesToCopy)
{
const AZStd::string nodeName = copyPasteData.GetNewNodeName(node, cutMode);
const int newNodeX = node->GetVisualPosX() + (posX - middlePosX);
const int newNodeY = node->GetVisualPosY() + (posY - middlePosY);
const AZStd::string commandString = AZStd::string::format("AnimGraphAdjustNode -animGraphID %d -name \"%s\" -xPos %d -yPos %d",
targetNode->GetAnimGraph()->GetID(),
nodeName.c_str(),
newNodeX,
newNodeY);
commandGroup->AddCommandString(commandString);
}
}
} // namespace CommandSystem