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

1353 lines
62 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 "AnimGraphConnectionCommands.h"
#include "AnimGraphNodeCommands.h"
#include "CommandManager.h"
#include <MCore/Source/LogManager.h>
#include <MCore/Source/ReflectionSerializer.h>
#include <EMotionFX/Source/ActorInstance.h>
#include <EMotionFX/Source/AnimGraph.h>
#include <EMotionFX/Source/BlendTreeParameterNode.h>
#include <EMotionFX/Source/AnimGraphInstance.h>
#include <EMotionFX/Source/AnimGraphManager.h>
#include <EMotionFX/Source/BlendTreeParameterNode.h>
#include <EMotionFX/Source/AnimGraphObjectFactory.h>
#include <EMotionFX/Source/AnimGraphMotionCondition.h>
#include <EMotionFX/Source/AnimGraphStateMachine.h>
#include <EMotionFX/Source/AnimGraphStateTransition.h>
#include <EMotionFX/Source/AnimGraphTransitionCondition.h>
#include <EMotionFX/Source/BlendTree.h>
#include <EMotionFX/Source/BlendTreeBlendNNode.h>
#include <EMotionFX/Source/ActorManager.h>
#include <EMotionFX/CommandSystem/Source/AnimGraphConditionCommands.h>
#include <EMotionFX/CommandSystem/Source/AnimGraphTriggerActionCommands.h>
namespace CommandSystem
{
EMotionFX::AnimGraph* CommandsGetAnimGraph(const MCore::CommandLine& parameters, MCore::Command* command, AZStd::string& outResult)
{
if (parameters.CheckIfHasParameter("animGraphID"))
{
const uint32 animGraphID = parameters.GetValueAsInt("animGraphID", command);
EMotionFX::AnimGraph* animGraph = EMotionFX::GetAnimGraphManager().FindAnimGraphByID(animGraphID);
if (animGraph == nullptr)
{
outResult = AZStd::string::format("Anim graph with id '%i' cannot be found.", animGraphID);
return nullptr;
}
return animGraph;
}
else
{
EMotionFX::AnimGraph* animGraph = GetCommandManager()->GetCurrentSelection().GetFirstAnimGraph();
if (animGraph == nullptr)
{
outResult = AZStd::string::format("Anim graph id is not specified and no one anim graph is selected.");
return nullptr;
}
return animGraph;
}
}
//-------------------------------------------------------------------------------------
// AnimGraphCreateConnection - create a connection between two nodes
//-------------------------------------------------------------------------------------
// constructor
CommandAnimGraphCreateConnection::CommandAnimGraphCreateConnection(MCore::Command* orgCommand)
: MCore::Command("AnimGraphCreateConnection", orgCommand)
{
mTransitionType = AZ::TypeId::CreateNull();
}
// destructor
CommandAnimGraphCreateConnection::~CommandAnimGraphCreateConnection()
{
}
// execute
bool CommandAnimGraphCreateConnection::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 transition type
AZ::Outcome<AZStd::string> transitionTypeString = parameters.GetValueIfExists("transitionType", this);
if (transitionTypeString.IsSuccess())
{
mTransitionType = AZ::TypeId::CreateString(transitionTypeString.GetValue().c_str());
}
// get the node names
AZStd::string sourceNodeName;
AZStd::string targetNodeName;
parameters.GetValue("sourceNode", "", sourceNodeName);
parameters.GetValue("targetNode", "", targetNodeName);
// find the source node in the anim graph
EMotionFX::AnimGraphNode* sourceNode = animGraph->RecursiveFindNodeByName(sourceNodeName.c_str());
// find the target node in the anim graph
EMotionFX::AnimGraphNode* targetNode = animGraph->RecursiveFindNodeByName(targetNodeName.c_str());
if (!targetNode)
{
outResult = AZStd::string::format("Cannot find target node with name '%s' in anim graph '%s'", targetNodeName.c_str(), animGraph->GetFileName());
return false;
}
if (targetNode == sourceNode)
{
outResult = AZStd::string::format("Unable to create connection: source node and target node are the same. Node name = %s", targetNode->GetName());
return false;
}
// get the ports
mSourcePort = parameters.GetValueAsInt("sourcePort", 0);
mTargetPort = parameters.GetValueAsInt("targetPort", 0);
parameters.GetValue("sourcePortName", this, mSourcePortName);
parameters.GetValue("targetPortName", this, mTargetPortName);
// in case the source port got specified by name, overwrite the source port number
if (!mSourcePortName.empty())
{
mSourcePort = sourceNode->FindOutputPortIndex(mSourcePortName.c_str());
// in case we want to add this connection to a parameter node while the parameter name doesn't exist, still return true so that copy paste doesn't fail
if (azrtti_typeid(sourceNode) == azrtti_typeid<EMotionFX::BlendTreeParameterNode>() && mSourcePort == -1)
{
m_connectionId.SetInvalid();
return true;
}
}
// in case the target port got specified by name, overwrite the target port number
if (!mTargetPortName.empty())
{
mTargetPort = targetNode->FindInputPortIndex(mTargetPortName.c_str());
}
// get the parent of the source node
if (targetNode->GetParentNode() == nullptr)
{
outResult = AZStd::string::format("Cannot create connections between root state machines.");
return false;
}
// if the parent is state machine, we don't need to check the port ranges
if (azrtti_typeid(targetNode->GetParentNode()) != azrtti_typeid<EMotionFX::AnimGraphStateMachine>())
{
if (sourceNode == nullptr)
{
outResult = AZStd::string::format("Cannot create blend tree connection in anim graph '%s' as the source node is not valid. ", animGraph->GetFileName());
return false;
}
// verify port ranges
if (mSourcePort >= static_cast<int32>(sourceNode->GetOutputPorts().size()) || mSourcePort < 0)
{
outResult = AZStd::string::format("The output port number is not valid for the given node. Node '%s' only has %zu output ports.", sourceNode->GetName(), sourceNode->GetOutputPorts().size());
return false;
}
if (mTargetPort >= static_cast<int32>(targetNode->GetInputPorts().size()) || mTargetPort < 0)
{
outResult = AZStd::string::format("The input port number is not valid for the given node. Node '%s' only has %zu input ports.", targetNode->GetName(), targetNode->GetInputPorts().size());
return false;
}
// check if connection already exists
if (targetNode->GetHasConnection(sourceNode, static_cast<uint16>(mSourcePort), static_cast<uint16>(mTargetPort)))
{
outResult = AZStd::string::format("The connection you are trying to create already exists!");
return false;
}
// create the connection and auto assign an id first of all
EMotionFX::BlendTreeConnection* connection = targetNode->AddConnection(sourceNode, static_cast<uint16>(mSourcePort), static_cast<uint16>(mTargetPort));
// Overwrite the connection id if specified by a command parameter.
if (parameters.CheckIfHasParameter("id"))
{
m_connectionId = EMotionFX::AnimGraphConnectionId::CreateFromString(parameters.GetValue("id", this));
connection->SetId(m_connectionId);
}
// In case of redo, use the connection id from the previous call.
else if (m_connectionId.IsValid())
{
connection->SetId(m_connectionId);
}
// Store the id for redo in case the connection got created with a new id.
else
{
m_connectionId = connection->GetId();
}
connection->Reinit();
if (azrtti_istypeof<EMotionFX::BlendTreeBlendNNode>(targetNode))
{
EMotionFX::BlendTreeBlendNNode* blendTreeBlendNNode = static_cast<EMotionFX::BlendTreeBlendNNode*>(targetNode);
mUpdateParamFlag = parameters.GetValueAsBool("updateParam", true);
if (mUpdateParamFlag)
{
blendTreeBlendNNode->UpdateParamWeights();
}
}
}
else // create a state transition
{
// get the state machine
EMotionFX::AnimGraphStateMachine* machine = (EMotionFX::AnimGraphStateMachine*)targetNode->GetParentNode();
// try to create the anim graph node
EMotionFX::AnimGraphObject* object = EMotionFX::AnimGraphObjectFactory::Create(mTransitionType, animGraph);
if (!object)
{
outResult = AZStd::string::format("Cannot create transition of type %s", mTransitionType.ToString<AZStd::string>().c_str());
return false;
}
// check if this is really a transition
if (!azrtti_istypeof<EMotionFX::AnimGraphStateTransition>(object))
{
outResult = AZStd::string::format("Cannot create state transition of type %s, because this object type is not inherited from AnimGraphStateTransition.", mTransitionType.ToString<AZStd::string>().c_str());
return false;
}
// typecast the anim graph node into a state transition node
EMotionFX::AnimGraphStateTransition* transition = static_cast<EMotionFX::AnimGraphStateTransition*>(object);
const EMotionFX::AnimGraphConnectionId idAfterCreation = transition->GetId();
// Deserialize first, manually specified parameters have higher priority and can overwrite the contents.
if (parameters.CheckIfHasParameter("contents"))
{
AZStd::string contents;
parameters.GetValue("contents", this, &contents);
MCore::ReflectionSerializer::Deserialize(transition, contents);
transition->RemoveAllConditions();
transition->GetTriggerActionSetup().RemoveAllActions();
}
// check if we are dealing with a wildcard transition
bool isWildcardTransition = false;
if (sourceNode == nullptr)
{
isWildcardTransition = true;
}
// setup the transition properties
transition->SetSourceNode(sourceNode);
transition->SetTargetNode(targetNode);
// get the offsets
mStartOffsetX = parameters.GetValueAsInt("startOffsetX", 0);
mStartOffsetY = parameters.GetValueAsInt("startOffsetY", 0);
mEndOffsetX = parameters.GetValueAsInt("endOffsetX", 0);
mEndOffsetY = parameters.GetValueAsInt("endOffsetY", 0);
if (parameters.CheckIfHasValue("startOffsetX") || parameters.CheckIfHasValue("startOffsetY") || parameters.CheckIfHasValue("endOffsetX") || parameters.CheckIfHasValue("endOffsetY"))
{
transition->SetVisualOffsets(mStartOffsetX, mStartOffsetY, mEndOffsetX, mEndOffsetY);
}
transition->SetIsWildcardTransition(isWildcardTransition);
// Overwrite the transition id if specified by a command parameter.
if (parameters.CheckIfHasParameter("id"))
{
m_connectionId = EMotionFX::AnimGraphConnectionId::CreateFromString(parameters.GetValue("id", this));
transition->SetId(m_connectionId);
}
// In case of redo, use the transition id from the previous call.
else if (m_connectionId.IsValid())
{
transition->SetId(m_connectionId);
}
// Store the id for redo in case the transition got created with a new id.
else
{
// Reassign the id after creation as the contents parameter might have overwritten the id.
m_connectionId = idAfterCreation;
transition->SetId(m_connectionId);
}
// add the transition to the state machine
machine->AddTransition(transition);
transition->Reinit();
}
mTargetNodeId.SetInvalid();
mSourceNodeId.SetInvalid();
if (targetNode)
{
mTargetNodeId = targetNode->GetId();
}
if (sourceNode)
{
mSourceNodeId = sourceNode->GetId();
}
// save the current dirty flag and tell the anim graph that something got changed
mOldDirtyFlag = animGraph->GetDirtyFlag();
animGraph->SetDirtyFlag(true);
// set the command result to the connection id
outResult = m_connectionId.ToString().c_str();
animGraph->RecursiveInvalidateUniqueDatas();
return true;
}
// undo the command
bool CommandAnimGraphCreateConnection::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;
}
// in case of a wildcard transition the source node is the invalid index, so that's all fine
EMotionFX::AnimGraphNode* sourceNode = animGraph->RecursiveFindNodeById(mSourceNodeId);
EMotionFX::AnimGraphNode* targetNode = animGraph->RecursiveFindNodeById(mTargetNodeId);
// NOTE: When source node is a nullptr, we are dealing with a wildcard transition, so there a nullptr is allowed.
if (!targetNode)
{
outResult = AZStd::string::format("Target node does not exist!");
return false;
}
// get the source node name, special path here as wildcard transitions have a nullptr source node
AZStd::string sourceNodeName;
if (sourceNode)
{
sourceNodeName = sourceNode->GetNameString();
}
// delete the connection
const AZStd::string commandString = AZStd::string::format("AnimGraphRemoveConnection -animGraphID %i -targetNode \"%s\" -targetPort %d -sourceNode \"%s\" -sourcePort %d -id %s",
animGraph->GetID(),
targetNode->GetName(),
mTargetPort,
sourceNodeName.c_str(),
mSourcePort,
m_connectionId.ToString().c_str());
// execute the command without putting it in the history
if (!GetCommandManager()->ExecuteCommandInsideCommand(commandString, outResult))
{
if (outResult.size() > 0)
{
MCore::LogError(outResult.c_str());
}
return false;
}
// reset the data used for undo and redo
mSourceNodeId.SetInvalid();
mTargetNodeId.SetInvalid();
// set the dirty flag back to the old value
animGraph->SetDirtyFlag(mOldDirtyFlag);
return true;
}
// init the syntax of the command
void CommandAnimGraphCreateConnection::InitSyntax()
{
GetSyntax().ReserveParameters(14);
GetSyntax().AddRequiredParameter("sourceNode", "The name of the source node, where the connection starts (output port).", MCore::CommandSyntax::PARAMTYPE_STRING);
GetSyntax().AddRequiredParameter("targetNode", "The name of the target node to connect to (input port).", MCore::CommandSyntax::PARAMTYPE_STRING);
GetSyntax().AddParameter("animGraphID", "The id of the anim graph to work on.", MCore::CommandSyntax::PARAMTYPE_INT, "-1");
GetSyntax().AddParameter("sourcePort", "The source port number where the connection starts inside the source node.", MCore::CommandSyntax::PARAMTYPE_INT, "-1");
GetSyntax().AddParameter("targetPort", "The target port number where the connection connects into, in the target node.", MCore::CommandSyntax::PARAMTYPE_INT, "-1");
GetSyntax().AddParameter("sourcePortName", "The source port name where the connection starts inside the source node.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("targetPortName", "The target port name where the connection connects into, in the target node.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("startOffsetX", "The start offset x position, which is the offset to from the upper left corner of the node where the connection starts from.", MCore::CommandSyntax::PARAMTYPE_INT, "0");
GetSyntax().AddParameter("startOffsetY", "The start offset y position, which is the offset to from the upper left corner of the node where the connection starts from.", MCore::CommandSyntax::PARAMTYPE_INT, "0");
GetSyntax().AddParameter("endOffsetX", "The end offset x position, which is the offset to from the upper left corner of the node where the connection ends.", MCore::CommandSyntax::PARAMTYPE_INT, "0");
GetSyntax().AddParameter("endOffsetY", "The end offset y position, which is the offset to from the upper left corner of the node where the connection ends.", MCore::CommandSyntax::PARAMTYPE_INT, "0");
GetSyntax().AddParameter("id", "The id of the connection.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("transitionType", "The transition type ID. This is the type ID (UUID) of the AnimGraphStateTransition inherited node types.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("contents", "The serialized contents of the parameter (in reflected XML).", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("updateParam", "The parameter of the connection, flag whether it needs to be updated.", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "true");
GetSyntax().AddParameter("updateUniqueData", "Setting this to true will trigger update on anim graph unique data.", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "true");
}
// get the description
const char* CommandAnimGraphCreateConnection::GetDescription() const
{
return "This command creates a connection between two anim graph nodes.";
}
//-------------------------------------------------------------------------------------
// AnimGraphRemoveConnection - create a connection between two nodes
//-------------------------------------------------------------------------------------
// constructor
CommandAnimGraphRemoveConnection::CommandAnimGraphRemoveConnection(MCore::Command* orgCommand)
: MCore::Command("AnimGraphRemoveConnection", orgCommand)
{
mSourcePort = MCORE_INVALIDINDEX32;
mTargetPort = MCORE_INVALIDINDEX32;
mTransitionType = AZ::TypeId::CreateNull();
mStartOffsetX = 0;
mStartOffsetY = 0;
mEndOffsetX = 0;
mEndOffsetY = 0;
}
// destructor
CommandAnimGraphRemoveConnection::~CommandAnimGraphRemoveConnection()
{
}
// execute
bool CommandAnimGraphRemoveConnection::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 node names
AZStd::string sourceNodeName;
AZStd::string targetNodeName;
parameters.GetValue("sourceNode", "", sourceNodeName);
parameters.GetValue("targetNode", "", targetNodeName);
// find the source node in the anim graph
EMotionFX::AnimGraphNode* sourceNode = animGraph->RecursiveFindNodeByName(sourceNodeName.c_str());
//if (sourceNode == nullptr)
//{
// outResult = AZStd::string::format("Cannot find source node with name '%s' in anim graph '%s'", sourceName.AsChar(), animGraph->GetName());
// return false;
//}
// find the target node in the anim graph
EMotionFX::AnimGraphNode* targetNode = animGraph->RecursiveFindNodeByName(targetNodeName.c_str());
if (targetNode == nullptr)
{
outResult = AZStd::string::format("Cannot find target node with name '%s' in anim graph '%s'", targetNodeName.c_str(), animGraph->GetFileName());
return false;
}
// get the ids from the source and destination nodes
mSourceNodeId.SetInvalid();
mSourceNodeName.clear();
if (sourceNode)
{
mSourceNodeId = sourceNode->GetId();
mSourceNodeName = sourceNode->GetName();
}
mTargetNodeId = targetNode->GetId();
mTargetNodeName = targetNode->GetName();
// get the ports
mSourcePort = parameters.GetValueAsInt("sourcePort", 0);
mTargetPort = parameters.GetValueAsInt("targetPort", 0);
// get the parent of the source node
if (targetNode->GetParentNode() == nullptr)
{
outResult = AZStd::string::format("Cannot remove connections between root state machines.");
return false;
}
// if the parent is state machine, we don't need to check the port ranges
if (azrtti_typeid(targetNode->GetParentNode()) != azrtti_typeid<EMotionFX::AnimGraphStateMachine>())
{
if (sourceNode == nullptr)
{
outResult = AZStd::string::format("Cannot remove blend tree connection in anim graph '%s' as the source node is not valid.", animGraph->GetFileName());
return false;
}
// verify port ranges
if (mSourcePort >= static_cast<int32>(sourceNode->GetOutputPorts().size()) || mSourcePort < 0)
{
outResult = AZStd::string::format("The output port number is not valid for the given node. Node '%s' only has %zu output ports.", sourceNode->GetName(), sourceNode->GetOutputPorts().size());
return false;
}
if (mTargetPort >= static_cast<int32>(targetNode->GetInputPorts().size()) || mTargetPort < 0)
{
outResult = AZStd::string::format("The input port number is not valid for the given node. Node '%s' only has %zu input ports.", targetNode->GetName(), targetNode->GetInputPorts().size());
return false;
}
// check if connection already exists
if (!targetNode->GetHasConnection(sourceNode, static_cast<uint16>(mSourcePort), static_cast<uint16>(mTargetPort)))
{
outResult = AZStd::string::format("The connection you are trying to remove doesn't exist!");
return false;
}
// get the connection ID and store it
EMotionFX::BlendTreeConnection* connection = targetNode->FindConnection(sourceNode, static_cast<uint16>(mSourcePort), static_cast<uint16>(mTargetPort));
if (connection)
{
m_connectionId = connection->GetId();
}
// create the connection
targetNode->RemoveConnection(sourceNode, static_cast<uint16>(mSourcePort), static_cast<uint16>(mTargetPort));
if (azrtti_istypeof<EMotionFX::BlendTreeBlendNNode>(targetNode))
{
EMotionFX::BlendTreeBlendNNode* blendTreeBlendNNode = static_cast<EMotionFX::BlendTreeBlendNNode*>(targetNode);
blendTreeBlendNNode->UpdateParamWeights();
}
}
else // remove a state transition
{
// get the transition id
EMotionFX::AnimGraphNodeId transitionId;
if (parameters.CheckIfHasParameter("id"))
{
transitionId = EMotionFX::AnimGraphConnectionId::CreateFromString(parameters.GetValue("id", this));
}
else
{
outResult = AZStd::string::format("You cannot remove a state transition with an invalid id. (Did you specify the id parameter?)");
return false;
}
// get the state machine
EMotionFX::AnimGraphStateMachine* stateMachine = static_cast<EMotionFX::AnimGraphStateMachine*>(targetNode->GetParentNode());
const AZ::Outcome<size_t> transitionIndex = stateMachine->FindTransitionIndexById(transitionId);
if (!transitionIndex.IsSuccess())
{
outResult = AZStd::string::format("The state transition you are trying to remove cannot be found.");
return false;
}
// save the transition information for undo
EMotionFX::AnimGraphStateTransition* transition = stateMachine->GetTransition(transitionIndex.GetValue());
mStartOffsetX = transition->GetVisualStartOffsetX();
mStartOffsetY = transition->GetVisualStartOffsetY();
mEndOffsetX = transition->GetVisualEndOffsetX();
mEndOffsetY = transition->GetVisualEndOffsetY();
mTransitionType = azrtti_typeid(transition);
m_connectionId = transition->GetId();
mOldContents = MCore::ReflectionSerializer::Serialize(transition).GetValue();
// remove all unique datas for the transition itself
animGraph->RemoveAllObjectData(transition, true);
// remove the transition
stateMachine->RemoveTransition(transitionIndex.GetValue());
}
// save the current dirty flag and tell the anim graph that something got changed
mOldDirtyFlag = animGraph->GetDirtyFlag();
animGraph->SetDirtyFlag(true);
if(parameters.GetValueAsBool("updateUniqueData", true))
{
animGraph->RecursiveInvalidateUniqueDatas();
}
return true;
}
// undo the command
bool CommandAnimGraphRemoveConnection::Undo(const MCore::CommandLine& parameters, AZStd::string& outResult)
{
const AZStd::string updateUniqueData = parameters.GetValue("updateUniqueData", this);
// 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;
}
if (!mTargetNodeId.IsValid())
{
return false;
}
AZStd::string commandString = AZStd::string::format("AnimGraphCreateConnection -animGraphID %i -sourceNode \"%s\" -targetNode \"%s\" -sourcePort %d -targetPort %d -startOffsetX %d -startOffsetY %d -endOffsetX %d -endOffsetY %d -id %s -transitionType \"%s\" -updateUniqueData %s",
animGraph->GetID(),
mSourceNodeName.c_str(),
mTargetNodeName.c_str(),
mSourcePort,
mTargetPort,
mStartOffsetX, mStartOffsetY,
mEndOffsetX, mEndOffsetY,
m_connectionId.ToString().c_str(),
mTransitionType.ToString<AZStd::string>().c_str(),
updateUniqueData.c_str());
// add the old attributes
if (mOldContents.empty() == false)
{
commandString += AZStd::string::format(" -contents {%s}", mOldContents.c_str());
}
if (!GetCommandManager()->ExecuteCommandInsideCommand(commandString, outResult))
{
if (outResult.size() > 0)
{
MCore::LogError(outResult.c_str());
}
return false;
}
mTargetNodeId.SetInvalid();
mSourceNodeId.SetInvalid();
m_connectionId.SetInvalid();
mSourcePort = MCORE_INVALIDINDEX32;
mTargetPort = MCORE_INVALIDINDEX32;
mStartOffsetX = 0;
mStartOffsetY = 0;
mEndOffsetX = 0;
mEndOffsetY = 0;
// set the dirty flag back to the old value
animGraph->SetDirtyFlag(mOldDirtyFlag);
return true;
}
// init the syntax of the command
void CommandAnimGraphRemoveConnection::InitSyntax()
{
// required
GetSyntax().ReserveParameters(7);
GetSyntax().AddRequiredParameter("animGraphID", "The id of the anim graph to work on.", MCore::CommandSyntax::PARAMTYPE_INT);
GetSyntax().AddRequiredParameter("sourceNode", "The name of the source node, where the connection starts (output port).", MCore::CommandSyntax::PARAMTYPE_STRING);
GetSyntax().AddRequiredParameter("targetNode", "The name of the target node where it connects to (input port).", MCore::CommandSyntax::PARAMTYPE_STRING);
GetSyntax().AddRequiredParameter("sourcePort", "The source port number where the connection starts inside the source node.", MCore::CommandSyntax::PARAMTYPE_INT);
GetSyntax().AddRequiredParameter("targetPort", "The target port number where the connection connects into, in the target node.", MCore::CommandSyntax::PARAMTYPE_INT);
GetSyntax().AddParameter("id", "The id of the connection.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("updateUniqueData", "Setting this to true will trigger update on anim graph unique data.", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "true");
}
// get the description
const char* CommandAnimGraphRemoveConnection::GetDescription() const
{
return "This command removes a connection between two anim graph nodes.";
}
//-------------------------------------------------------------------------------------
// CommandAnimGraphAdjustTransition
//-------------------------------------------------------------------------------------
void AdjustTransition(const EMotionFX::AnimGraphStateTransition* transition,
const AZStd::optional<bool>& isDisabled,
const AZStd::optional<AZStd::string>& sourceNode, const AZStd::optional<AZStd::string>& targetNode,
const AZStd::optional<AZ::s32>& startOffsetX, const AZStd::optional<AZ::s32>& startOffsetY,
const AZStd::optional<AZ::s32>& endOffsetX, const AZStd::optional<AZ::s32>& endOffsetY,
const AZStd::optional<AZStd::string>& attributesString, const AZStd::optional<AZStd::string>& serializedMembers,
MCore::CommandGroup* commandGroup, bool executeInsideCommand)
{
AZStd::string command = AZStd::string::format("%s -%s %i -%s %s",
CommandAnimGraphAdjustTransition::s_commandName,
EMotionFX::ParameterMixinAnimGraphId::s_parameterName,
transition->GetAnimGraph()->GetID(),
EMotionFX::ParameterMixinTransitionId::s_parameterName,
transition->GetId().ToString().c_str());
if (isDisabled)
{
command += AZStd::string::format(" -isDisabled %s", AZStd::to_string(isDisabled.value()).c_str());
}
if (startOffsetX)
{
command += AZStd::string::format(" -startOffsetX %i", startOffsetX.value());
}
if (startOffsetY)
{
command += AZStd::string::format(" -startOffsetY %i", startOffsetY.value());
}
if (endOffsetX)
{
command += AZStd::string::format(" -endOffsetX %i", endOffsetX.value());
}
if (endOffsetY)
{
command += AZStd::string::format(" -endOffsetY %i", endOffsetY.value());
}
if (sourceNode)
{
command += AZStd::string::format(" -sourceNode \"%s\"", sourceNode.value().c_str());
}
if (targetNode)
{
command += AZStd::string::format(" -targetNode \"%s\"", targetNode.value().c_str());
}
if (attributesString)
{
command += AZStd::string::format(" -%s {", EMotionFX::ParameterMixinAttributesString::s_parameterName);
command += attributesString.value();
command += "}";
}
if (serializedMembers)
{
command += AZStd::string::format(" -%s {", EMotionFX::ParameterMixinSerializedMembers::s_parameterName);
command += serializedMembers.value();
command += "}";
}
CommandSystem::GetCommandManager()->ExecuteCommandOrAddToGroup(command, commandGroup, executeInsideCommand);
}
AZ_CLASS_ALLOCATOR_IMPL(CommandAnimGraphAdjustTransition, EMotionFX::CommandAllocator, 0)
const char* CommandAnimGraphAdjustTransition::s_commandName = "AnimGraphAdjustTransition";
CommandAnimGraphAdjustTransition::CommandAnimGraphAdjustTransition(MCore::Command* orgCommand)
: MCore::Command(s_commandName, orgCommand)
{
}
void CommandAnimGraphAdjustTransition::RewindTransitionIfActive(EMotionFX::AnimGraphStateTransition* transition)
{
const EMotionFX::AnimGraph* animGraph = transition->GetAnimGraph();
EMotionFX::AnimGraphStateMachine* stateMachine = transition->GetStateMachine();
const size_t numAnimGraphInstances = animGraph->GetNumAnimGraphInstances();
for (size_t i = 0; i < numAnimGraphInstances; ++i)
{
EMotionFX::AnimGraphInstance* animGraphInstance = animGraph->GetAnimGraphInstance(i);
if (stateMachine->IsTransitionActive(transition, animGraphInstance))
{
stateMachine->Rewind(animGraphInstance);
}
}
}
bool CommandAnimGraphAdjustTransition::Execute(const MCore::CommandLine& parameters, AZStd::string& outResult)
{
EMotionFX::AnimGraph* animGraph = GetAnimGraph(outResult);
if (!animGraph)
{
return false;
}
EMotionFX::AnimGraphStateTransition* transition = GetTransition(animGraph, outResult);
if (!transition)
{
return false;
}
m_oldSerializedMembers = MCore::ReflectionSerializer::SerializeMembersExcept(transition, {"conditions", "actionSetup"});
// set the new source node
if (parameters.CheckIfHasParameter("sourceNode"))
{
const AZStd::string newSourceName = parameters.GetValue("sourceNode", this);
EMotionFX::AnimGraphNode* newSourceNode = animGraph->RecursiveFindNodeByName(newSourceName.c_str());
if (!newSourceNode)
{
outResult = AZStd::string::format("Cannot find new source node with name '%s' in anim graph '%s'", newSourceName.c_str(), animGraph->GetFileName());
return false;
}
RewindTransitionIfActive(transition);
transition->SetSourceNode(newSourceNode);
}
// set the new target node
if (parameters.CheckIfHasParameter("targetNode"))
{
const AZStd::string newTargetName = parameters.GetValue("targetNode", this);
EMotionFX::AnimGraphNode* newTargetNode = animGraph->RecursiveFindNodeByName(newTargetName.c_str());
if (!newTargetNode)
{
outResult = AZStd::string::format("Cannot find new target node with name '%s' in anim graph '%s'", newTargetName.c_str(), animGraph->GetFileName());
return false;
}
RewindTransitionIfActive(transition);
transition->SetTargetNode(newTargetNode);
}
// set the new visual offsets
if (parameters.CheckIfHasParameter("startOffsetX") && parameters.CheckIfHasParameter("startOffsetY") &&
parameters.CheckIfHasParameter("endOffsetX") && parameters.CheckIfHasParameter("endOffsetY"))
{
const int32 newStartOffsetX = parameters.GetValueAsInt("startOffsetX", this);
const int32 newStartOffsetY = parameters.GetValueAsInt("startOffsetY", this);
const int32 newEndOffsetX = parameters.GetValueAsInt("endOffsetX", this);
const int32 newEndOffsetY = parameters.GetValueAsInt("endOffsetY", this);
transition->SetVisualOffsets(newStartOffsetX, newStartOffsetY, newEndOffsetX, newEndOffsetY);
}
// set the disabled flag
if (parameters.CheckIfHasParameter("isDisabled"))
{
const bool isDisabled = parameters.GetValueAsBool("isDisabled", this);
transition->SetIsDisabled(isDisabled);
}
const AZStd::optional<AZStd::string>& attributesString = ParameterMixinAttributesString::GetAttributesString();
if (attributesString)
{
MCore::ReflectionSerializer::Deserialize(transition, MCore::CommandLine(attributesString.value()));
}
const AZStd::optional<AZStd::string>& serializedMembers = ParameterMixinSerializedMembers::GetSerializedMembers();
if (serializedMembers)
{
MCore::ReflectionSerializer::DeserializeMembers(transition, serializedMembers.value());
}
// save the current dirty flag and tell the anim graph that something got changed
mOldDirtyFlag = animGraph->GetDirtyFlag();
animGraph->SetDirtyFlag(true);
transition->Reinit();
animGraph->RecursiveInvalidateUniqueDatas();
outResult = m_transitionId.ToString().c_str();
return true;
}
bool CommandAnimGraphAdjustTransition::Undo(const MCore::CommandLine& parameters, AZStd::string& outResult)
{
AZ_UNUSED(parameters);
EMotionFX::AnimGraph* animGraph = GetAnimGraph(outResult);
if (!animGraph)
{
return false;
}
EMotionFX::AnimGraphStateTransition* transition = GetTransition(animGraph, outResult);
if (!transition)
{
return false;
}
AdjustTransition(transition,
/*mOldDisabledFlag=*/AZStd::nullopt,
/*sourceNodeName=*/AZStd::nullopt, /*targetNodeName=*/AZStd::nullopt,
/*startOffsetX=*/AZStd::nullopt, /*startOffsetY=*/AZStd::nullopt,
/*endOffsetX=*/AZStd::nullopt, /*endOffsetY=*/AZStd::nullopt,
/*attributesString=*/AZStd::nullopt, /*serializedMembers=*/m_oldSerializedMembers.GetValue(),
/*commandGroup*/nullptr, /*executeInsideCommand*/true);
animGraph->SetDirtyFlag(mOldDirtyFlag);
return true;
}
void CommandAnimGraphAdjustTransition::InitSyntax()
{
GetSyntax().ReserveParameters(13);
MCore::CommandSyntax& syntax = GetSyntax();
ParameterMixinTransitionId::InitSyntax(syntax);
ParameterMixinAttributesString::InitSyntax(syntax, false);
ParameterMixinSerializedMembers::InitSyntax(syntax, false);
GetSyntax().AddParameter("sourceNode", "The new source node of the transition.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("targetNode", "The new target node of the transition.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("startOffsetX", ".", MCore::CommandSyntax::PARAMTYPE_INT, "0");
GetSyntax().AddParameter("startOffsetY", ".", MCore::CommandSyntax::PARAMTYPE_INT, "0");
GetSyntax().AddParameter("endOffsetX", ".", MCore::CommandSyntax::PARAMTYPE_INT, "0");
GetSyntax().AddParameter("endOffsetY", ".", MCore::CommandSyntax::PARAMTYPE_INT, "0");
GetSyntax().AddParameter("isDisabled", "False in case the transition shall be active and working, true in case it should be disabled and act like it does not exist.", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "true");
GetSyntax().AddParameter("attributesString", "The connection attributes as string.", MCore::CommandSyntax::PARAMTYPE_STRING, "");
GetSyntax().AddParameter("updateUniqueData", "Setting this to true will trigger update on anim graph unique data.", MCore::CommandSyntax::PARAMTYPE_BOOLEAN, "true");
}
bool CommandAnimGraphAdjustTransition::SetCommandParameters(const MCore::CommandLine& parameters)
{
ParameterMixinTransitionId::SetCommandParameters(parameters);
ParameterMixinAttributesString::SetCommandParameters(parameters);
ParameterMixinSerializedMembers::SetCommandParameters(parameters);
return true;
}
const char* CommandAnimGraphAdjustTransition::GetDescription() const
{
return "";
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void DeleteNodeConnection(MCore::CommandGroup* commandGroup, const EMotionFX::AnimGraphNode* node, const EMotionFX::BlendTreeConnection* connection, bool updateUniqueData)
{
const AZStd::string commandString = AZStd::string::format("AnimGraphRemoveConnection -animGraphID %d -targetNode \"%s\" -targetPort %d -sourceNode \"%s\" -sourcePort %d -updateUniqueData %s",
node->GetAnimGraph()->GetID(),
node->GetName(),
connection->GetTargetPort(),
connection->GetSourceNode()->GetName(),
connection->GetSourcePort(),
updateUniqueData ? "true" : "false");
commandGroup->AddCommandString(commandString);
}
void CreateNodeConnection(MCore::CommandGroup* commandGroup, EMotionFX::AnimGraphNode* targetNode, EMotionFX::BlendTreeConnection* connection)
{
const AZStd::string commandString = AZStd::string::format("AnimGraphCreateConnection -animGraphID %i -sourceNode \"%s\" -targetNode \"%s\" -sourcePort %d -targetPort %d",
targetNode->GetAnimGraph()->GetID(),
connection->GetSourceNode()->GetName(),
targetNode->GetName(),
connection->GetSourcePort(),
connection->GetTargetPort());
commandGroup->AddCommandString(commandString);
}
// Delete a given connection.
void DeleteConnection(MCore::CommandGroup* commandGroup, EMotionFX::AnimGraphNode* node, EMotionFX::BlendTreeConnection* connection, AZStd::vector<EMotionFX::BlendTreeConnection*>& connectionList)
{
// Skip directly if the connection is already in the list.
if (AZStd::find(connectionList.begin(), connectionList.end(), connection) != connectionList.end())
{
return;
}
// In case the source node is specified, get the node name from the connection.
AZStd::string sourceNodeName;
if (connection->GetSourceNode())
{
sourceNodeName = connection->GetSourceNode()->GetName();
}
const AZStd::string commandString = AZStd::string::format("AnimGraphRemoveConnection -animGraphID %i -targetNode \"%s\" -targetPort %d -sourceNode \"%s\" -sourcePort %d",
node->GetAnimGraph()->GetID(),
node->GetName(),
connection->GetTargetPort(),
sourceNodeName.c_str(),
connection->GetSourcePort());
connectionList.push_back(connection);
commandGroup->AddCommandString(commandString);
}
// Delete all incoming and outgoing connections for the given node.
void DeleteNodeConnections(MCore::CommandGroup* commandGroup, EMotionFX::AnimGraphNode* node, EMotionFX::AnimGraphNode* parentNode, AZStd::vector<EMotionFX::BlendTreeConnection*>& connectionList, bool recursive)
{
// Delete the connections that start from the given node.
if (parentNode)
{
const uint32 numChildNodes = parentNode->GetNumChildNodes();
for (uint32 i = 0; i < numChildNodes; ++i)
{
EMotionFX::AnimGraphNode* childNode = parentNode->GetChildNode(i);
if (childNode == node)
{
continue;
}
const uint32 numChildConnections = childNode->GetNumConnections();
for (uint32 j = 0; j < numChildConnections; ++j)
{
EMotionFX::BlendTreeConnection* childConnection = childNode->GetConnection(j);
// If the connection starts at the given node, delete it.
if (childConnection->GetSourceNode() == node)
{
DeleteConnection(commandGroup, childNode, childConnection, connectionList);
}
}
}
}
// Delete the connections that end in the given node.
const uint32 numConnections = node->GetNumConnections();
for (uint32 i = 0; i < numConnections; ++i)
{
EMotionFX::BlendTreeConnection* connection = node->GetConnection(i);
DeleteConnection(commandGroup, node, connection, connectionList);
}
// Recursively delete all connections.
if (recursive)
{
const uint32 numChildNodes = node->GetNumChildNodes();
for (uint32 i = 0; i < numChildNodes; ++i)
{
EMotionFX::AnimGraphNode* childNode = node->GetChildNode(i);
DeleteNodeConnections(commandGroup, childNode, node, connectionList, recursive);
}
}
}
// Relink the given connection from the given node to a new target node.
void RelinkConnectionTarget(MCore::CommandGroup* commandGroup, const uint32 animGraphID, const char* sourceNodeName, uint32 sourcePortNr, const char* oldTargetNodeName, uint32 oldTargetPortNr, const char* newTargetNodeName, uint32 newTargetPortNr)
{
AZStd::string commandString;
// Delete the old connection first.
commandString = AZStd::string::format("AnimGraphRemoveConnection -animGraphID %i -targetNode \"%s\" -targetPort %d -sourceNode \"%s\" -sourcePort %d",
animGraphID,
oldTargetNodeName,
oldTargetPortNr,
sourceNodeName,
sourcePortNr);
commandGroup->AddCommandString(commandString);
// Create the new connection.
commandString = AZStd::string::format("AnimGraphCreateConnection -animGraphID %i -sourceNode \"%s\" -targetNode \"%s\" -sourcePort %d -targetPort %d",
animGraphID,
sourceNodeName,
newTargetNodeName,
sourcePortNr,
newTargetPortNr);
commandGroup->AddCommandString(commandString);
}
// Relink connection to a new source node and or port.
void RelinkConnectionSource(MCore::CommandGroup* commandGroup, const uint32 animGraphID, const char* oldSourceNodeName, uint32 oldSourcePortNr, const char* newSourceNodeName, uint32 newSourcePortNr, const char* targetNodeName, uint32 targetPortNr)
{
AZStd::string commandString;
// Delete the old connection first.
commandString = AZStd::string::format("AnimGraphRemoveConnection -animGraphID %i -targetNode \"%s\" -targetPort %d -sourceNode \"%s\" -sourcePort %d",
animGraphID,
targetNodeName,
targetPortNr,
oldSourceNodeName,
oldSourcePortNr);
commandGroup->AddCommandString(commandString);
// Create the new connection.
commandString = AZStd::string::format("AnimGraphCreateConnection -animGraphID %i -sourceNode \"%s\" -targetNode \"%s\" -sourcePort %d -targetPort %d",
animGraphID,
newSourceNodeName,
targetNodeName,
newSourcePortNr,
targetPortNr);
commandGroup->AddCommandString(commandString);
}
void DeleteStateTransition(MCore::CommandGroup* commandGroup, EMotionFX::AnimGraphStateTransition* transition, AZStd::vector<EMotionFX::AnimGraphStateTransition*>& transitionList)
{
// Skip directly if the transition is already in the list.
if (AZStd::find(transitionList.begin(), transitionList.end(), transition) != transitionList.end())
{
return;
}
const EMotionFX::AnimGraphNode* sourceNode = transition->GetSourceNode();
const EMotionFX::AnimGraphNode* targetNode = transition->GetTargetNode();
const EMotionFX::AnimGraphStateMachine* stateMachine = azdynamic_cast<const EMotionFX::AnimGraphStateMachine*>(targetNode->GetParentNode());
// Safety check, we need to be working with states, not blend tree nodes.
if (!stateMachine)
{
AZ_Error("EMotionFX", false, "Cannot delete state transition. The anim graph node named '%s' is not a state.", targetNode->GetName());
return;
}
// Remove the transition that is about to be removed from all other transition's can be interrupted by transition id masks.
AZStd::string attributesString;
const size_t numTransitions = stateMachine->GetNumTransitions();
for (size_t i = 0; i < numTransitions; ++i)
{
EMotionFX::AnimGraphStateTransition* checkTransition = stateMachine->GetTransition(i);
AZStd::vector<AZ::u64> canBeInterruptedByTransitionIds = checkTransition->GetCanBeInterruptedByTransitionIds();
if (checkTransition != transition &&
AZStd::find(canBeInterruptedByTransitionIds.begin(), canBeInterruptedByTransitionIds.end(), transition->GetId()) != canBeInterruptedByTransitionIds.end() &&
AZStd::find(transitionList.begin(), transitionList.end(), checkTransition) == transitionList.end()) // Skip in case the transition already got added to the command group to be removed.
{
// Get the can be interrupted by transition ids vector and remove the transition about to be
// removed as well as all the already removed transtions within the same command group from it.
canBeInterruptedByTransitionIds.erase(AZStd::remove(canBeInterruptedByTransitionIds.begin(),
canBeInterruptedByTransitionIds.end(),
transition->GetId()),
canBeInterruptedByTransitionIds.end());
for (EMotionFX::AnimGraphStateTransition* alreadyRemovedTranstion : transitionList)
{
canBeInterruptedByTransitionIds.erase(AZStd::remove(canBeInterruptedByTransitionIds.begin(),
canBeInterruptedByTransitionIds.end(),
alreadyRemovedTranstion->GetId()),
canBeInterruptedByTransitionIds.end());
}
// Serialize the attribute into a string so we can pass it as a command parameter.
attributesString = AZStd::string::format("-canBeInterruptedByTransitionIds {%s}",
MCore::ReflectionSerializer::Serialize(&canBeInterruptedByTransitionIds).GetValue().c_str());
// Construct the command and let it adjust the can be interrupted by transition id mask.
AdjustTransition(checkTransition,
/*isDisabled*/AZStd::nullopt, /*sourceNode*/AZStd::nullopt, /*targetNode*/AZStd::nullopt,
/*startOffsetX*/AZStd::nullopt, /*startOffsetY*/AZStd::nullopt, /*endOffsetX*/AZStd::nullopt, /*endOffsetY*/AZStd::nullopt,
attributesString, /*serializedMembers=*/AZStd::nullopt,
commandGroup);
}
}
// Remove transition actions back to front.
const size_t numActions = transition->GetTriggerActionSetup().GetNumActions();
for (size_t i = 0; i < numActions; ++i)
{
const size_t actionIndex = numActions - i - 1;
RemoveTransitionAction(transition, actionIndex, commandGroup);
}
// Remove transition conditions back to front.
const size_t numConditions = transition->GetNumConditions();
for (size_t i = 0; i < numConditions; ++i)
{
const size_t conditionIndex = numConditions - i - 1;
CommandRemoveTransitionCondition* removeConditionCommand = aznew CommandRemoveTransitionCondition(transition->GetAnimGraph()->GetID(), transition->GetId(), conditionIndex);
commandGroup->AddCommand(removeConditionCommand);
}
// If we are dealing with a wildcard transition, reset the source node so that we use the empty name for that.
if (transition->GetIsWildcardTransition())
{
sourceNode = nullptr;
}
// If the source node is specified, get the node name.
AZStd::string sourceNodeName;
if (sourceNode)
{
sourceNodeName = sourceNode->GetName();
}
const AZStd::string commandString = AZStd::string::format("AnimGraphRemoveConnection -animGraphID %i -sourceNode \"%s\" -targetNode \"%s\" -targetPort 0 -sourcePort 0 -id %s",
targetNode->GetAnimGraph()->GetID(),
sourceNodeName.c_str(),
targetNode->GetName(),
transition->GetId().ToString().c_str());
commandGroup->AddCommandString(commandString);
transitionList.push_back(transition);
}
// Delete all incoming and outgoing transitions for the given node.
void DeleteStateTransitions(MCore::CommandGroup* commandGroup, EMotionFX::AnimGraphNode* state, EMotionFX::AnimGraphNode* parentNode, AZStd::vector<EMotionFX::AnimGraphStateTransition*>& transitionList, bool recursive)
{
// Only do for state machines.
if (parentNode && azrtti_typeid(parentNode) == azrtti_typeid<EMotionFX::AnimGraphStateMachine>())
{
EMotionFX::AnimGraphStateMachine* stateMachine = static_cast<EMotionFX::AnimGraphStateMachine*>(parentNode);
const size_t numTransitions = stateMachine->GetNumTransitions();
for (size_t j = 0; j < numTransitions; ++j)
{
EMotionFX::AnimGraphStateTransition* transition = stateMachine->GetTransition(j);
const EMotionFX::AnimGraphNode* sourceNode = transition->GetSourceNode();
const EMotionFX::AnimGraphNode* targetNode = transition->GetTargetNode();
// If the connection starts at the given node, delete it.
if (targetNode == state || (!transition->GetIsWildcardTransition() && sourceNode == state))
{
DeleteStateTransition(commandGroup, transition, transitionList);
}
}
}
// Recursively delete all transitions.
if (recursive)
{
const uint32 numChildNodes = state->GetNumChildNodes();
for (uint32 i = 0; i < numChildNodes; ++i)
{
EMotionFX::AnimGraphNode* childNode = state->GetChildNode(i);
DeleteStateTransitions(commandGroup, childNode, state, transitionList, recursive);
}
}
}
void CopyStateTransition(MCore::CommandGroup* commandGroup, EMotionFX::AnimGraph* targetAnimGraph, EMotionFX::AnimGraphStateTransition* transition,
bool cutMode, AZStd::unordered_map<AZ::u64, AZ::u64>& convertedIds, AnimGraphCopyPasteData& copyPasteData)
{
EMotionFX::AnimGraphNode* sourceNode = transition->GetSourceNode();
EMotionFX::AnimGraphNode* targetNode = transition->GetTargetNode();
// We only copy transitions that are between nodes that are copied. Otherwise, the transition doesn't have a valid origin/target. If the transition is a wildcard
// we only need the target.
if (copyPasteData.m_newNamesByCopiedNodes.find(targetNode) == copyPasteData.m_newNamesByCopiedNodes.end() ||
(!transition->GetIsWildcardTransition() && copyPasteData.m_newNamesByCopiedNodes.find(sourceNode) == copyPasteData.m_newNamesByCopiedNodes.end()))
{
return;
}
AZStd::string sourceName;
if (!transition->GetIsWildcardTransition() && sourceNode)
{
// In case the source node is going to get copied too get the new name, if not just use name of the source node of the connection.
sourceName = copyPasteData.GetNewNodeName(sourceNode, cutMode);
}
const AZStd::string targetName = copyPasteData.GetNewNodeName(targetNode, cutMode);
const EMotionFX::AnimGraphConnectionId newTransitionId = copyPasteData.GetNewConnectionId(transition->GetId(), cutMode);
// Relink the interruption candidates, serialize the transition contents and set it back to its original state.
const AZStd::vector<AZ::u64> oldCanBeInterruptedByTransitionIds = transition->GetCanBeInterruptedByTransitionIds();
AZStd::vector<AZ::u64> canBeInterruptedByTransitionIds = transition->GetCanBeInterruptedByTransitionIds();
for (AZ::u64& id : canBeInterruptedByTransitionIds)
{
id = copyPasteData.GetNewConnectionId(id, cutMode);
}
transition->SetCanBeInterruptedBy(canBeInterruptedByTransitionIds);
const AZStd::string serializedTransition = MCore::ReflectionSerializer::Serialize(transition).GetValue();
transition->SetCanBeInterruptedBy(oldCanBeInterruptedByTransitionIds);
AZStd::string commandString = AZStd::string::format("AnimGraphCreateConnection -animGraphID %d -sourceNode \"%s\" -targetNode \"%s\" -sourcePort %d -targetPort %d -transitionType \"%s\" -id %s -contents {%s}",
targetAnimGraph->GetID(),
sourceName.c_str(),
targetName.c_str(),
0, // sourcePort
0, // targetPort
azrtti_typeid(transition).ToString<AZStd::string>().c_str(),
newTransitionId.ToString().c_str(),
serializedTransition.c_str());
commandGroup->AddCommandString(commandString);
// Find the name of the state machine
EMotionFX::AnimGraphStateMachine* stateMachine = static_cast<EMotionFX::AnimGraphStateMachine*>(targetNode->GetParentNode());
const AZStd::string stateMachineName = copyPasteData.GetNewNodeName(stateMachine, cutMode);
if (!cutMode)
{
AZStd::string attributesString;
transition->GetAttributeStringForAffectedNodeIds(convertedIds, attributesString);
if (!attributesString.empty())
{
commandString = AZStd::string::format("%s -%s %d -%s %s -attributesString {%s}",
CommandAnimGraphAdjustTransition::s_commandName,
EMotionFX::ParameterMixinAnimGraphId::s_parameterName, targetAnimGraph->GetID(),
EMotionFX::ParameterMixinTransitionId::s_parameterName, newTransitionId.ToString().c_str(),
attributesString.c_str());
commandGroup->AddCommandString(commandString);
}
}
const size_t numConditions = transition->GetNumConditions();
for (size_t i = 0; i < numConditions; ++i)
{
EMotionFX::AnimGraphTransitionCondition* condition = transition->GetCondition(i);
const AZ::TypeId conditionType = azrtti_typeid(condition);
commandString = AZStd::string::format("%s -%s %d -%s %s -conditionType \"%s\" -contents {%s}",
CommandSystem::CommandAddTransitionCondition::s_commandName,
EMotionFX::ParameterMixinAnimGraphId::s_parameterName, targetAnimGraph->GetID(),
EMotionFX::ParameterMixinTransitionId::s_parameterName, newTransitionId.ToString().c_str(),
conditionType.ToString<AZStd::string>().c_str(),
MCore::ReflectionSerializer::Serialize(condition).GetValue().c_str());
commandGroup->AddCommandString(commandString);
if (!cutMode)
{
AZStd::string attributesString;
condition->GetAttributeStringForAffectedNodeIds(convertedIds, attributesString);
if (!attributesString.empty())
{
CommandSystem::CommandAdjustTransitionCondition* adjustConditionCommand = aznew CommandSystem::CommandAdjustTransitionCondition(
targetAnimGraph->GetID(),
newTransitionId,
/*conditionIndex=*/i,
attributesString);
commandGroup->AddCommand(adjustConditionCommand);
}
}
}
const AZStd::vector<EMotionFX::AnimGraphTriggerAction*> actions = transition->GetTriggerActionSetup().GetActions();
for (const EMotionFX::AnimGraphTriggerAction* action : actions)
{
CommandSystem::AddTransitionAction(targetAnimGraph->GetID(),
newTransitionId.ToString().c_str(),
azrtti_typeid(action),
MCore::ReflectionSerializer::Serialize(action).GetValue(),
/*insertAt=*/AZStd::nullopt,
commandGroup);
}
}
void CopyBlendTreeConnection(MCore::CommandGroup* commandGroup, EMotionFX::AnimGraph* targetAnimGraph, EMotionFX::AnimGraphNode* targetNode, EMotionFX::BlendTreeConnection* connection,
bool cutMode, AZStd::unordered_map<AZ::u64, AZ::u64>& convertedIds, AnimGraphCopyPasteData& copyPasteData)
{
AZ_UNUSED(convertedIds);
EMotionFX::AnimGraphNode* sourceNode = connection->GetSourceNode();
if (!sourceNode)
{
return;
}
// Only copy connections that are between nodes that are copied.
if (copyPasteData.m_newNamesByCopiedNodes.find(sourceNode) == copyPasteData.m_newNamesByCopiedNodes.end() ||
copyPasteData.m_newNamesByCopiedNodes.find(targetNode) == copyPasteData.m_newNamesByCopiedNodes.end())
{
return;
}
const AZStd::string sourceNodeName = copyPasteData.GetNewNodeName(sourceNode, cutMode);
const AZStd::string targetNodeName = copyPasteData.GetNewNodeName(targetNode, cutMode);
const uint16 sourcePort = connection->GetSourcePort();
const uint16 targetPort = connection->GetTargetPort();
const AZStd::string commandString = AZStd::string::format("AnimGraphCreateConnection -animGraphID %u -sourceNode \"%s\" -targetNode \"%s\" -sourcePortName \"%s\" -targetPortName \"%s\" -updateParam \"false\"",
targetAnimGraph->GetID(),
sourceNodeName.c_str(),
targetNodeName.c_str(),
sourceNode->GetOutputPort(sourcePort).GetName(),
targetNode->GetInputPort(targetPort).GetName());
commandGroup->AddCommandString(commandString);
}
} // namesapce EMotionFX