You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Improved blend profile support adding a time based method. #jira UE-100992 #review @Aaron.Cox, @Jose.Villarroel, @Thomas.Sarkanen [CL 14831887 by john vanderburg in ue5-main branch]
598 lines
23 KiB
C++
598 lines
23 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "AnimGraphNode_StateMachineBase.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
|
|
#include "Kismet2/Kismet2NameValidators.h"
|
|
|
|
#include "AnimationGraph.h"
|
|
#include "AnimationStateMachineGraph.h"
|
|
#include "AnimationStateMachineSchema.h"
|
|
#include "AnimGraphNode_StateMachine.h"
|
|
#include "Kismet2/KismetEditorUtilities.h"
|
|
#include "AnimBlueprintCompiler.h"
|
|
#include "AnimStateNode.h"
|
|
#include "AnimStateTransitionNode.h"
|
|
#include "AnimGraphNode_TransitionResult.h"
|
|
#include "AnimGraphNode_StateResult.h"
|
|
#include "AnimStateEntryNode.h"
|
|
#include "AnimationStateGraph.h"
|
|
#include "AnimationTransitionGraph.h"
|
|
#include "AnimationCustomTransitionGraph.h"
|
|
#include "AnimGraphNode_SequencePlayer.h"
|
|
#include "AnimStateConduitNode.h"
|
|
#include "AnimGraphNode_LinkedAnimLayer.h"
|
|
#include "AnimGraphNode_TransitionPoseEvaluator.h"
|
|
#include "AnimGraphNode_CustomTransitionResult.h"
|
|
#include "AnimBlueprintCompilerHandler_StateMachine.h"
|
|
#include "IAnimBlueprintGeneratedClassCompiledData.h"
|
|
#include "IAnimBlueprintCompilationContext.h"
|
|
|
|
/////////////////////////////////////////////////////
|
|
// FAnimStateMachineNodeNameValidator
|
|
|
|
class FAnimStateMachineNodeNameValidator : public FStringSetNameValidator
|
|
{
|
|
public:
|
|
FAnimStateMachineNodeNameValidator(const UAnimGraphNode_StateMachineBase* InStateMachineNode)
|
|
: FStringSetNameValidator(FString())
|
|
{
|
|
TArray<UAnimGraphNode_StateMachineBase*> Nodes;
|
|
|
|
UAnimationGraph* StateMachine = CastChecked<UAnimationGraph>(InStateMachineNode->GetOuter());
|
|
StateMachine->GetNodesOfClassEx<UAnimGraphNode_StateMachine, UAnimGraphNode_StateMachineBase>(Nodes);
|
|
|
|
for (auto Node : Nodes)
|
|
{
|
|
if (Node != InStateMachineNode)
|
|
{
|
|
Names.Add(Node->GetStateMachineName());
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/////////////////////////////////////////////////////
|
|
// UAnimGraphNode_StateMachineBase
|
|
|
|
#define LOCTEXT_NAMESPACE "A3Nodes"
|
|
|
|
UAnimGraphNode_StateMachineBase::UAnimGraphNode_StateMachineBase(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
}
|
|
|
|
FLinearColor UAnimGraphNode_StateMachineBase::GetNodeTitleColor() const
|
|
{
|
|
return FLinearColor(0.8f, 0.8f, 0.8f);
|
|
}
|
|
|
|
FText UAnimGraphNode_StateMachineBase::GetTooltipText() const
|
|
{
|
|
return LOCTEXT("StateMachineTooltip", "Animation State Machine");
|
|
}
|
|
|
|
FText UAnimGraphNode_StateMachineBase::GetNodeTitle(ENodeTitleType::Type TitleType) const
|
|
{
|
|
if ((TitleType == ENodeTitleType::MenuTitle || TitleType == ENodeTitleType::ListView) && (EditorStateMachineGraph == nullptr))
|
|
{
|
|
return LOCTEXT("AddNewStateMachine", "Add New State Machine...");
|
|
}
|
|
else if (EditorStateMachineGraph == nullptr)
|
|
{
|
|
if (TitleType == ENodeTitleType::FullTitle)
|
|
{
|
|
return LOCTEXT("NullStateMachineFullTitle", "Error: No Graph\nState Machine");
|
|
}
|
|
else
|
|
{
|
|
return LOCTEXT("ErrorNoGraph", "Error: No Graph");
|
|
}
|
|
}
|
|
else if (TitleType == ENodeTitleType::FullTitle)
|
|
{
|
|
if (CachedFullTitle.IsOutOfDate(this))
|
|
{
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("Title"), FText::FromName(EditorStateMachineGraph->GetFName()));
|
|
// FText::Format() is slow, so we cache this to save on performance
|
|
CachedFullTitle.SetCachedText(FText::Format(LOCTEXT("StateMachineFullTitle", "{Title}\nState Machine"), Args), this);
|
|
}
|
|
return CachedFullTitle;
|
|
}
|
|
|
|
return FText::FromName(EditorStateMachineGraph->GetFName());
|
|
}
|
|
|
|
FString UAnimGraphNode_StateMachineBase::GetNodeCategory() const
|
|
{
|
|
return TEXT("State Machines");
|
|
}
|
|
|
|
void UAnimGraphNode_StateMachineBase::PostPlacedNewNode()
|
|
{
|
|
// Create a new animation graph
|
|
check(EditorStateMachineGraph == NULL);
|
|
EditorStateMachineGraph = CastChecked<UAnimationStateMachineGraph>(FBlueprintEditorUtils::CreateNewGraph(this, NAME_None, UAnimationStateMachineGraph::StaticClass(), UAnimationStateMachineSchema::StaticClass()));
|
|
check(EditorStateMachineGraph);
|
|
EditorStateMachineGraph->OwnerAnimGraphNode = this;
|
|
|
|
// Find an interesting name
|
|
TSharedPtr<INameValidatorInterface> NameValidator = FNameValidatorFactory::MakeValidator(this);
|
|
FBlueprintEditorUtils::RenameGraphWithSuggestion(EditorStateMachineGraph, NameValidator, TEXT("New State Machine"));
|
|
|
|
// Initialize the anim graph
|
|
const UEdGraphSchema* Schema = EditorStateMachineGraph->GetSchema();
|
|
Schema->CreateDefaultNodesForGraph(*EditorStateMachineGraph);
|
|
|
|
// Add the new graph as a child of our parent graph
|
|
UEdGraph* ParentGraph = GetGraph();
|
|
|
|
if(ParentGraph->SubGraphs.Find(EditorStateMachineGraph) == INDEX_NONE)
|
|
{
|
|
ParentGraph->Modify();
|
|
ParentGraph->SubGraphs.Add(EditorStateMachineGraph);
|
|
}
|
|
}
|
|
|
|
UObject* UAnimGraphNode_StateMachineBase::GetJumpTargetForDoubleClick() const
|
|
{
|
|
// Open the state machine graph
|
|
return EditorStateMachineGraph;
|
|
}
|
|
|
|
void UAnimGraphNode_StateMachineBase::JumpToDefinition() const
|
|
{
|
|
if (UObject* HyperlinkTarget = GetJumpTargetForDoubleClick())
|
|
{
|
|
FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(HyperlinkTarget);
|
|
}
|
|
}
|
|
|
|
void UAnimGraphNode_StateMachineBase::DestroyNode()
|
|
{
|
|
UEdGraph* GraphToRemove = EditorStateMachineGraph;
|
|
|
|
EditorStateMachineGraph = NULL;
|
|
Super::DestroyNode();
|
|
|
|
if (GraphToRemove)
|
|
{
|
|
UBlueprint* Blueprint = GetBlueprint();
|
|
GraphToRemove->Modify();
|
|
FBlueprintEditorUtils::RemoveGraph(Blueprint, GraphToRemove, EGraphRemoveFlags::Recompile);
|
|
}
|
|
}
|
|
|
|
void UAnimGraphNode_StateMachineBase::PostPasteNode()
|
|
{
|
|
Super::PostPasteNode();
|
|
|
|
// Add the new graph as a child of our parent graph
|
|
UEdGraph* ParentGraph = GetGraph();
|
|
|
|
if(ParentGraph->SubGraphs.Find(EditorStateMachineGraph) == INDEX_NONE)
|
|
{
|
|
ParentGraph->SubGraphs.Add(EditorStateMachineGraph);
|
|
}
|
|
|
|
for (UEdGraphNode* GraphNode : EditorStateMachineGraph->Nodes)
|
|
{
|
|
GraphNode->CreateNewGuid();
|
|
GraphNode->PostPasteNode();
|
|
GraphNode->ReconstructNode();
|
|
}
|
|
|
|
// Find an interesting name
|
|
TSharedPtr<INameValidatorInterface> NameValidator = FNameValidatorFactory::MakeValidator(this);
|
|
FBlueprintEditorUtils::RenameGraphWithSuggestion(EditorStateMachineGraph, NameValidator, EditorStateMachineGraph->GetName());
|
|
|
|
//restore transactional flag that is lost during copy/paste process
|
|
EditorStateMachineGraph->SetFlags(RF_Transactional);
|
|
}
|
|
|
|
FString UAnimGraphNode_StateMachineBase::GetStateMachineName()
|
|
{
|
|
return (EditorStateMachineGraph != NULL) ? *(EditorStateMachineGraph->GetName()) : TEXT("(null)");
|
|
}
|
|
|
|
TSharedPtr<class INameValidatorInterface> UAnimGraphNode_StateMachineBase::MakeNameValidator() const
|
|
{
|
|
return MakeShareable(new FAnimStateMachineNodeNameValidator(this));
|
|
}
|
|
|
|
FString UAnimGraphNode_StateMachineBase::GetDocumentationLink() const
|
|
{
|
|
return TEXT("Shared/GraphNodes/AnimationStateMachine");
|
|
}
|
|
|
|
void UAnimGraphNode_StateMachineBase::OnRenameNode(const FString& NewName)
|
|
{
|
|
FBlueprintEditorUtils::RenameGraph(EditorStateMachineGraph, NewName);
|
|
}
|
|
|
|
void UAnimGraphNode_StateMachineBase::OnProcessDuringCompilation(IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData)
|
|
{
|
|
struct FMachineCreator
|
|
{
|
|
public:
|
|
int32 MachineIndex;
|
|
TMap<UAnimStateNodeBase*, int32> StateIndexTable;
|
|
TMap<UAnimStateTransitionNode*, int32> TransitionIndexTable;
|
|
UAnimGraphNode_StateMachineBase* StateMachineInstance;
|
|
TArray<FBakedAnimationStateMachine>& BakedMachines;
|
|
IAnimBlueprintGeneratedClassCompiledData& CompiledData;
|
|
IAnimBlueprintCompilationContext& CompilationContext;
|
|
|
|
public:
|
|
FMachineCreator(UAnimGraphNode_StateMachineBase* InStateMachineInstance, int32 InMachineIndex, TArray<FBakedAnimationStateMachine>& InBakedMachines, IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& InCompiledData)
|
|
: MachineIndex(InMachineIndex)
|
|
, StateMachineInstance(InStateMachineInstance)
|
|
, BakedMachines(InBakedMachines)
|
|
, CompiledData(InCompiledData)
|
|
, CompilationContext(InCompilationContext)
|
|
{
|
|
FStateMachineDebugData& MachineInfo = GetMachineSpecificDebugData();
|
|
MachineInfo.MachineIndex = MachineIndex;
|
|
MachineInfo.MachineInstanceNode = CompilationContext.GetMessageLog().FindSourceObjectTypeChecked<UAnimGraphNode_StateMachineBase>(InStateMachineInstance);
|
|
|
|
StateMachineInstance->GetNode().StateMachineIndexInClass = MachineIndex;
|
|
|
|
FBakedAnimationStateMachine& BakedMachine = GetMachine();
|
|
BakedMachine.MachineName = StateMachineInstance->EditorStateMachineGraph->GetFName();
|
|
BakedMachine.InitialState = INDEX_NONE;
|
|
}
|
|
|
|
FBakedAnimationStateMachine& GetMachine()
|
|
{
|
|
return BakedMachines[MachineIndex];
|
|
}
|
|
|
|
FStateMachineDebugData& GetMachineSpecificDebugData()
|
|
{
|
|
UAnimationStateMachineGraph* SourceGraph = CompilationContext.GetMessageLog().FindSourceObjectTypeChecked<UAnimationStateMachineGraph>(StateMachineInstance->EditorStateMachineGraph);
|
|
return CompiledData.GetAnimBlueprintDebugData().StateMachineDebugData.FindOrAdd(SourceGraph);
|
|
}
|
|
|
|
int32 FindOrAddState(UAnimStateNodeBase* StateNode)
|
|
{
|
|
if (int32* pResult = StateIndexTable.Find(StateNode))
|
|
{
|
|
return *pResult;
|
|
}
|
|
else
|
|
{
|
|
FBakedAnimationStateMachine& BakedMachine = GetMachine();
|
|
|
|
const int32 StateIndex = BakedMachine.States.Num();
|
|
StateIndexTable.Add(StateNode, StateIndex);
|
|
new (BakedMachine.States) FBakedAnimationState();
|
|
|
|
UAnimStateNodeBase* SourceNode = CompilationContext.GetMessageLog().FindSourceObjectTypeChecked<UAnimStateNodeBase>(StateNode);
|
|
GetMachineSpecificDebugData().NodeToStateIndex.Add(SourceNode, StateIndex);
|
|
if (UAnimStateNode* SourceStateNode = Cast<UAnimStateNode>(SourceNode))
|
|
{
|
|
CompiledData.GetAnimBlueprintDebugData().StateGraphToNodeMap.Add(SourceStateNode->BoundGraph, SourceStateNode);
|
|
}
|
|
|
|
return StateIndex;
|
|
}
|
|
}
|
|
|
|
int32 FindOrAddTransition(UAnimStateTransitionNode* TransitionNode)
|
|
{
|
|
if (int32* pResult = TransitionIndexTable.Find(TransitionNode))
|
|
{
|
|
return *pResult;
|
|
}
|
|
else
|
|
{
|
|
FBakedAnimationStateMachine& BakedMachine = GetMachine();
|
|
|
|
const int32 TransitionIndex = BakedMachine.Transitions.Num();
|
|
TransitionIndexTable.Add(TransitionNode, TransitionIndex);
|
|
new (BakedMachine.Transitions) FAnimationTransitionBetweenStates();
|
|
|
|
UAnimStateTransitionNode* SourceTransitionNode = CompilationContext.GetMessageLog().FindSourceObjectTypeChecked<UAnimStateTransitionNode>(TransitionNode);
|
|
GetMachineSpecificDebugData().NodeToTransitionIndex.Add(SourceTransitionNode, TransitionIndex);
|
|
CompiledData.GetAnimBlueprintDebugData().TransitionGraphToNodeMap.Add(SourceTransitionNode->BoundGraph, SourceTransitionNode);
|
|
|
|
if (SourceTransitionNode->CustomTransitionGraph != NULL)
|
|
{
|
|
CompiledData.GetAnimBlueprintDebugData().TransitionBlendGraphToNodeMap.Add(SourceTransitionNode->CustomTransitionGraph, SourceTransitionNode);
|
|
}
|
|
|
|
return TransitionIndex;
|
|
}
|
|
}
|
|
|
|
void Validate()
|
|
{
|
|
FBakedAnimationStateMachine& BakedMachine = GetMachine();
|
|
|
|
// Make sure there is a valid entry point
|
|
if (BakedMachine.InitialState == INDEX_NONE)
|
|
{
|
|
CompilationContext.GetMessageLog().Warning(*LOCTEXT("NoEntryNode", "There was no entry state connection in @@").ToString(), StateMachineInstance);
|
|
BakedMachine.InitialState = 0;
|
|
}
|
|
else
|
|
{
|
|
// Make sure the entry node is a state and not a conduit
|
|
if (BakedMachine.States[BakedMachine.InitialState].bIsAConduit)
|
|
{
|
|
UEdGraphNode* StateNode = GetMachineSpecificDebugData().FindNodeFromStateIndex(BakedMachine.InitialState);
|
|
CompilationContext.GetMessageLog().Error(*LOCTEXT("BadStateEntryNode", "A conduit (@@) cannot be used as the entry node for a state machine").ToString(), StateNode);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
FAnimBlueprintCompilerHandler_StateMachine* CompilerHandler = InCompilationContext.GetHandler<FAnimBlueprintCompilerHandler_StateMachine>("AnimBlueprintCompilerHandler_StateMachine");
|
|
check(CompilerHandler);
|
|
|
|
if (EditorStateMachineGraph == NULL)
|
|
{
|
|
InCompilationContext.GetMessageLog().Error(*LOCTEXT("BadStateMachineNoGraph", "@@ does not have a corresponding graph").ToString(), this);
|
|
return;
|
|
}
|
|
|
|
TMap<UAnimGraphNode_TransitionResult*, int32> AlreadyMergedTransitionList;
|
|
|
|
TArray<FBakedAnimationStateMachine>& BakedStateMachines = OutCompiledData.GetBakedStateMachines();
|
|
const int32 MachineIndex = BakedStateMachines.AddDefaulted();
|
|
FMachineCreator Oven(this, MachineIndex, BakedStateMachines, InCompilationContext, OutCompiledData);
|
|
|
|
// Map of states that contain a single player node (from state root node index to associated sequence player)
|
|
TMap<int32, UObject*> SimplePlayerStatesMap;
|
|
|
|
// Process all the states/transitions
|
|
for (auto StateNodeIt = EditorStateMachineGraph->Nodes.CreateIterator(); StateNodeIt; ++StateNodeIt)
|
|
{
|
|
UEdGraphNode* Node = *StateNodeIt;
|
|
|
|
if (UAnimStateEntryNode* EntryNode = Cast<UAnimStateEntryNode>(Node))
|
|
{
|
|
// Handle the state graph entry
|
|
FBakedAnimationStateMachine& BakedMachine = Oven.GetMachine();
|
|
if (BakedMachine.InitialState != INDEX_NONE)
|
|
{
|
|
InCompilationContext.GetMessageLog().Error(*LOCTEXT("TooManyStateMachineEntryNodes", "Found an extra entry node @@").ToString(), EntryNode);
|
|
}
|
|
else if (UAnimStateNodeBase* StartState = Cast<UAnimStateNodeBase>(EntryNode->GetOutputNode()))
|
|
{
|
|
BakedMachine.InitialState = Oven.FindOrAddState(StartState);
|
|
}
|
|
else
|
|
{
|
|
InCompilationContext.GetMessageLog().Warning(*LOCTEXT("NoConnection", "Entry node @@ is not connected to state").ToString(), EntryNode);
|
|
}
|
|
}
|
|
else if (UAnimStateTransitionNode* TransitionNode = Cast<UAnimStateTransitionNode>(Node))
|
|
{
|
|
TransitionNode->ValidateNodeDuringCompilation(InCompilationContext.GetMessageLog());
|
|
|
|
const int32 TransitionIndex = Oven.FindOrAddTransition(TransitionNode);
|
|
FAnimationTransitionBetweenStates& BakedTransition = Oven.GetMachine().Transitions[TransitionIndex];
|
|
|
|
BakedTransition.CrossfadeDuration = TransitionNode->CrossfadeDuration;
|
|
BakedTransition.StartNotify = OutCompiledData.FindOrAddNotify(TransitionNode->TransitionStart);
|
|
BakedTransition.EndNotify = OutCompiledData.FindOrAddNotify(TransitionNode->TransitionEnd);
|
|
BakedTransition.InterruptNotify = OutCompiledData.FindOrAddNotify(TransitionNode->TransitionInterrupt);
|
|
BakedTransition.BlendMode = TransitionNode->BlendMode;
|
|
BakedTransition.CustomCurve = TransitionNode->CustomBlendCurve;
|
|
BakedTransition.BlendProfile = TransitionNode->BlendProfile;
|
|
BakedTransition.LogicType = TransitionNode->LogicType;
|
|
|
|
UAnimStateNodeBase* PreviousState = TransitionNode->GetPreviousState();
|
|
UAnimStateNodeBase* NextState = TransitionNode->GetNextState();
|
|
|
|
if ((PreviousState != NULL) && (NextState != NULL))
|
|
{
|
|
const int32 PreviousStateIndex = Oven.FindOrAddState(PreviousState);
|
|
const int32 NextStateIndex = Oven.FindOrAddState(NextState);
|
|
|
|
if (TransitionNode->Bidirectional)
|
|
{
|
|
InCompilationContext.GetMessageLog().Warning(*LOCTEXT("BidirectionalTransWarning", "Bidirectional transitions aren't supported yet @@").ToString(), TransitionNode);
|
|
}
|
|
|
|
BakedTransition.PreviousState = PreviousStateIndex;
|
|
BakedTransition.NextState = NextStateIndex;
|
|
}
|
|
else
|
|
{
|
|
InCompilationContext.GetMessageLog().Warning(*LOCTEXT("BogusTransition", "@@ is incomplete, without a previous and next state").ToString(), TransitionNode);
|
|
BakedTransition.PreviousState = INDEX_NONE;
|
|
BakedTransition.NextState = INDEX_NONE;
|
|
}
|
|
}
|
|
else if (UAnimStateNode* StateNode = Cast<UAnimStateNode>(Node))
|
|
{
|
|
StateNode->ValidateNodeDuringCompilation(InCompilationContext.GetMessageLog());
|
|
|
|
const int32 StateIndex = Oven.FindOrAddState(StateNode);
|
|
FBakedAnimationState& BakedState = Oven.GetMachine().States[StateIndex];
|
|
|
|
if (StateNode->BoundGraph != NULL)
|
|
{
|
|
BakedState.StateName = StateNode->BoundGraph->GetFName();
|
|
BakedState.StartNotify = OutCompiledData.FindOrAddNotify(StateNode->StateEntered);
|
|
BakedState.EndNotify = OutCompiledData.FindOrAddNotify(StateNode->StateLeft);
|
|
BakedState.FullyBlendedNotify = OutCompiledData.FindOrAddNotify(StateNode->StateFullyBlended);
|
|
BakedState.bIsAConduit = false;
|
|
BakedState.bAlwaysResetOnEntry = StateNode->bAlwaysResetOnEntry;
|
|
|
|
// Process the inner graph of this state
|
|
if (UAnimGraphNode_StateResult* AnimGraphResultNode = CastChecked<UAnimationStateGraph>(StateNode->BoundGraph)->GetResultNode())
|
|
{
|
|
InCompilationContext.ValidateGraphIsWellFormed(StateNode->BoundGraph);
|
|
|
|
BakedState.StateRootNodeIndex = CompilerHandler->ExpandGraphAndProcessNodes(StateNode->BoundGraph, AnimGraphResultNode, InCompilationContext, OutCompiledData);
|
|
|
|
// See if the state consists of a single sequence player node, and remember the index if so
|
|
for (UEdGraphPin* TestPin : AnimGraphResultNode->Pins)
|
|
{
|
|
if ((TestPin->Direction == EGPD_Input) && (TestPin->LinkedTo.Num() == 1))
|
|
{
|
|
if (UAnimGraphNode_SequencePlayer* SequencePlayer = Cast<UAnimGraphNode_SequencePlayer>(TestPin->LinkedTo[0]->GetOwningNode()))
|
|
{
|
|
SimplePlayerStatesMap.Add(BakedState.StateRootNodeIndex, InCompilationContext.GetMessageLog().FindSourceObject(SequencePlayer));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BakedState.StateRootNodeIndex = INDEX_NONE;
|
|
InCompilationContext.GetMessageLog().Error(*LOCTEXT("StateWithNoResult", "@@ has no result node").ToString(), StateNode);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BakedState.StateName = NAME_None;
|
|
InCompilationContext.GetMessageLog().Error(*LOCTEXT("StateWithBadGraph", "@@ has no bound graph").ToString(), StateNode);
|
|
}
|
|
|
|
// If this check fires, then something in the machine has changed causing the states array to not
|
|
// be a separate allocation, and a state machine inside of this one caused stuff to shift around
|
|
checkSlow(&BakedState == &(Oven.GetMachine().States[StateIndex]));
|
|
}
|
|
else if (UAnimStateConduitNode* ConduitNode = Cast<UAnimStateConduitNode>(Node))
|
|
{
|
|
ConduitNode->ValidateNodeDuringCompilation(InCompilationContext.GetMessageLog());
|
|
|
|
const int32 StateIndex = Oven.FindOrAddState(ConduitNode);
|
|
FBakedAnimationState& BakedState = Oven.GetMachine().States[StateIndex];
|
|
|
|
BakedState.StateName = ConduitNode->BoundGraph ? ConduitNode->BoundGraph->GetFName() : TEXT("OLD CONDUIT");
|
|
BakedState.bIsAConduit = true;
|
|
|
|
if (ConduitNode->BoundGraph != NULL)
|
|
{
|
|
if (UAnimGraphNode_TransitionResult* EntryRuleResultNode = CastChecked<UAnimationTransitionGraph>(ConduitNode->BoundGraph)->GetResultNode())
|
|
{
|
|
BakedState.EntryRuleNodeIndex = CompilerHandler->ExpandGraphAndProcessNodes(ConduitNode->BoundGraph, EntryRuleResultNode, InCompilationContext, OutCompiledData);
|
|
}
|
|
}
|
|
|
|
// If this check fires, then something in the machine has changed causing the states array to not
|
|
// be a separate allocation, and a state machine inside of this one caused stuff to shift around
|
|
checkSlow(&BakedState == &(Oven.GetMachine().States[StateIndex]));
|
|
}
|
|
}
|
|
|
|
// Process transitions after all the states because getters within custom graphs may want to
|
|
// reference back to other states, which are only valid if they have already been baked
|
|
for (auto StateNodeIt = Oven.StateIndexTable.CreateIterator(); StateNodeIt; ++StateNodeIt)
|
|
{
|
|
UAnimStateNodeBase* StateNode = StateNodeIt.Key();
|
|
const int32 StateIndex = StateNodeIt.Value();
|
|
|
|
FBakedAnimationState& BakedState = Oven.GetMachine().States[StateIndex];
|
|
|
|
// Add indices to all player and layer nodes
|
|
TArray<UEdGraph*> GraphsToCheck;
|
|
GraphsToCheck.Add(StateNode->GetBoundGraph());
|
|
StateNode->GetBoundGraph()->GetAllChildrenGraphs(GraphsToCheck);
|
|
|
|
TArray<UAnimGraphNode_LinkedAnimLayer*> LinkedAnimLayerNodes;
|
|
TArray<UAnimGraphNode_AssetPlayerBase*> AssetPlayerNodes;
|
|
for (UEdGraph* ChildGraph : GraphsToCheck)
|
|
{
|
|
ChildGraph->GetNodesOfClass(AssetPlayerNodes);
|
|
ChildGraph->GetNodesOfClass(LinkedAnimLayerNodes);
|
|
}
|
|
|
|
for (UAnimGraphNode_AssetPlayerBase* Node : AssetPlayerNodes)
|
|
{
|
|
if (int32* IndexPtr = OutCompiledData.GetAnimBlueprintDebugData().NodeGuidToIndexMap.Find(Node->NodeGuid))
|
|
{
|
|
BakedState.PlayerNodeIndices.Add(*IndexPtr);
|
|
}
|
|
}
|
|
|
|
for (UAnimGraphNode_LinkedAnimLayer* Node : LinkedAnimLayerNodes)
|
|
{
|
|
if (int32* IndexPtr = OutCompiledData.GetAnimBlueprintDebugData().NodeGuidToIndexMap.Find(Node->NodeGuid))
|
|
{
|
|
BakedState.LayerNodeIndices.Add(*IndexPtr);
|
|
}
|
|
}
|
|
// Handle all the transitions out of this node
|
|
TArray<class UAnimStateTransitionNode*> TransitionList;
|
|
StateNode->GetTransitionList(/*out*/ TransitionList, /*bWantSortedList=*/ true);
|
|
|
|
for (auto TransitionIt = TransitionList.CreateIterator(); TransitionIt; ++TransitionIt)
|
|
{
|
|
UAnimStateTransitionNode* TransitionNode = *TransitionIt;
|
|
const int32 TransitionIndex = Oven.FindOrAddTransition(TransitionNode);
|
|
|
|
// Validate the blend profile for this transition - incase the skeleton of the node has
|
|
// changed or the blend profile no longer exists.
|
|
TransitionNode->ValidateBlendProfile();
|
|
|
|
FBakedStateExitTransition& Rule = *new (BakedState.Transitions) FBakedStateExitTransition();
|
|
Rule.bDesiredTransitionReturnValue = (TransitionNode->GetPreviousState() == StateNode);
|
|
Rule.TransitionIndex = TransitionIndex;
|
|
|
|
if (UAnimGraphNode_TransitionResult* TransitionResultNode = CastChecked<UAnimationTransitionGraph>(TransitionNode->BoundGraph)->GetResultNode())
|
|
{
|
|
if (int32* pIndex = AlreadyMergedTransitionList.Find(TransitionResultNode))
|
|
{
|
|
Rule.CanTakeDelegateIndex = *pIndex;
|
|
}
|
|
else
|
|
{
|
|
Rule.CanTakeDelegateIndex = CompilerHandler->ExpandGraphAndProcessNodes(TransitionNode->BoundGraph, TransitionResultNode, InCompilationContext, OutCompiledData, TransitionNode);
|
|
AlreadyMergedTransitionList.Add(TransitionResultNode, Rule.CanTakeDelegateIndex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Rule.CanTakeDelegateIndex = INDEX_NONE;
|
|
InCompilationContext.GetMessageLog().Error(*LOCTEXT("TransitionWithNoResult", "@@ has no result node").ToString(), TransitionNode);
|
|
}
|
|
|
|
// Handle automatic time remaining rules
|
|
Rule.bAutomaticRemainingTimeRule = TransitionNode->bAutomaticRuleBasedOnSequencePlayerInState;
|
|
Rule.SyncGroupNameToRequireValidMarkersRule = TransitionNode->SyncGroupNameToRequireValidMarkersRule;
|
|
|
|
// Handle custom transition graphs
|
|
Rule.CustomResultNodeIndex = INDEX_NONE;
|
|
if (UAnimationCustomTransitionGraph* CustomTransitionGraph = Cast<UAnimationCustomTransitionGraph>(TransitionNode->CustomTransitionGraph))
|
|
{
|
|
TArray<UEdGraphNode*> ClonedNodes;
|
|
if (CustomTransitionGraph->GetResultNode())
|
|
{
|
|
Rule.CustomResultNodeIndex = CompilerHandler->ExpandGraphAndProcessNodes(TransitionNode->CustomTransitionGraph, CustomTransitionGraph->GetResultNode(), InCompilationContext, OutCompiledData, nullptr, &ClonedNodes);
|
|
}
|
|
|
|
// Find all the pose evaluators used in this transition, save handles to them because we need to populate some pose data before executing
|
|
TArray<UAnimGraphNode_TransitionPoseEvaluator*> TransitionPoseList;
|
|
for (auto ClonedNodesIt = ClonedNodes.CreateIterator(); ClonedNodesIt; ++ClonedNodesIt)
|
|
{
|
|
UEdGraphNode* Node = *ClonedNodesIt;
|
|
if (UAnimGraphNode_TransitionPoseEvaluator* TypedNode = Cast<UAnimGraphNode_TransitionPoseEvaluator>(Node))
|
|
{
|
|
TransitionPoseList.Add(TypedNode);
|
|
}
|
|
}
|
|
|
|
Rule.PoseEvaluatorLinks.Empty(TransitionPoseList.Num());
|
|
|
|
for (auto TransitionPoseListIt = TransitionPoseList.CreateIterator(); TransitionPoseListIt; ++TransitionPoseListIt)
|
|
{
|
|
UAnimGraphNode_TransitionPoseEvaluator* TransitionPoseNode = *TransitionPoseListIt;
|
|
Rule.PoseEvaluatorLinks.Add( InCompilationContext.GetAllocationIndexOfNode(TransitionPoseNode) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Oven.Validate();
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|