Files
UnrealEngineUWP/Engine/Source/Editor/GraphEditor/Private/ConnectionDrawingPolicy.cpp
2014-10-24 13:26:17 -04:00

1439 lines
52 KiB
C++

// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#include "GraphEditorCommon.h"
#include "ConnectionDrawingPolicy.h"
#include "Editor/UnrealEd/Public/Kismet2/KismetDebugUtilities.h"
#include "Editor/UnrealEd/Public/Kismet2/BlueprintEditorUtils.h"
#include "KismetNodes/KismetNodeInfoContext.h"
#include "SoundDefinitions.h"
#include "AnimationStateNodes/SGraphNodeAnimTransition.h"
#include "AnimStateTransitionNode.h"
#include "AnimStateEntryNode.h"
#include "AnimationGraphSchema.h"
#include "AnimGraphNode_Base.h"
#include "Sound/SoundNode.h"
#include "K2Node_Knot.h"
#include "BlueprintEditorSettings.h"
DEFINE_LOG_CATEGORY_STATIC(LogConnectionDrawingPolicy, Log, All);
//////////////////////////////////////////////////////////////////////////
// FGeometryHelper
FVector2D FGeometryHelper::VerticalMiddleLeftOf(const FGeometry& SomeGeometry)
{
const FVector2D GeometryDrawSize = SomeGeometry.GetDrawSize();
return FVector2D(
SomeGeometry.AbsolutePosition.X,
SomeGeometry.AbsolutePosition.Y + GeometryDrawSize.Y/2 );
}
FVector2D FGeometryHelper::VerticalMiddleRightOf(const FGeometry& SomeGeometry)
{
const FVector2D GeometryDrawSize = SomeGeometry.GetDrawSize();
return FVector2D(
SomeGeometry.AbsolutePosition.X + GeometryDrawSize.X,
SomeGeometry.AbsolutePosition.Y + GeometryDrawSize.Y/2 );
}
FVector2D FGeometryHelper::CenterOf(const FGeometry& SomeGeometry)
{
const FVector2D GeometryDrawSize = SomeGeometry.GetDrawSize();
return SomeGeometry.AbsolutePosition + (GeometryDrawSize * 0.5f);
}
void FGeometryHelper::ConvertToPoints(const FGeometry& Geom, TArray<FVector2D>& Points)
{
const FVector2D Size = Geom.GetDrawSize();
const FVector2D Location = Geom.AbsolutePosition;
int32 Index = Points.AddUninitialized(4);
Points[Index++] = Location;
Points[Index++] = Location + FVector2D(0.0f, Size.Y);
Points[Index++] = Location + FVector2D(Size.X, Size.Y);
Points[Index++] = Location + FVector2D(Size.X, 0.0f);
}
/** Find the point on line segment from LineStart to LineEnd which is closest to Point */
FVector2D FGeometryHelper::FindClosestPointOnLine(const FVector2D& LineStart, const FVector2D& LineEnd, const FVector2D& TestPoint)
{
const FVector2D LineVector = LineEnd - LineStart;
const float A = -FVector2D::DotProduct(LineStart - TestPoint, LineVector);
const float B = LineVector.SizeSquared();
const float T = FMath::Clamp<float>(A / B, 0.0f, 1.0f);
// Generate closest point
return LineStart + (T * LineVector);
}
FVector2D FGeometryHelper::FindClosestPointOnGeom(const FGeometry& Geom, const FVector2D& TestPoint)
{
TArray<FVector2D> Points;
FGeometryHelper::ConvertToPoints(Geom, Points);
float BestDistanceSquared = MAX_FLT;
FVector2D BestPoint;
for (int32 i = 0; i < Points.Num(); ++i)
{
const FVector2D Candidate = FindClosestPointOnLine(Points[i], Points[(i + 1) % Points.Num()], TestPoint);
const float CandidateDistanceSquared = (Candidate-TestPoint).SizeSquared();
if (CandidateDistanceSquared < BestDistanceSquared)
{
BestPoint = Candidate;
BestDistanceSquared = CandidateDistanceSquared;
}
}
return BestPoint;
}
//////////////////////////////////////////////////////////////////////////
// FKismetNodeInfoContext
// Context used to aid debugging displays for nodes
FKismetNodeInfoContext::FKismetNodeInfoContext(UEdGraph* SourceGraph)
: ActiveObjectBeingDebugged(NULL)
{
// Only show pending latent actions in PIE/SIE mode
SourceBlueprint = FBlueprintEditorUtils::FindBlueprintForGraph(SourceGraph);
if (SourceBlueprint != NULL)
{
ActiveObjectBeingDebugged = SourceBlueprint->GetObjectBeingDebugged();
// Run thru debugged objects to see if any are objects with pending latent actions
if (ActiveObjectBeingDebugged != NULL)
{
UBlueprintGeneratedClass* Class = CastChecked<UBlueprintGeneratedClass>((UObject*)(ActiveObjectBeingDebugged->GetClass()));
FBlueprintDebugData const& ClassDebugData = Class->GetDebugData();
TSet<UObject*> LatentContextObjects;
TArray<UK2Node_CallFunction*> FunctionNodes;
SourceGraph->GetNodesOfClass<UK2Node_CallFunction>(FunctionNodes);
// collect all the world context objects for all of the graph's latent nodes
for (UK2Node_CallFunction const* FunctionNode : FunctionNodes)
{
UFunction* Function = FunctionNode->GetTargetFunction();
if ((Function == NULL) || !Function->HasMetaData(FBlueprintMetadata::MD_Latent))
{
continue;
}
UObject* NodeWorldContext = ActiveObjectBeingDebugged;
// if the node has a specific "world context" pin, attempt to get the value set for that first
if (Function->HasMetaData(FBlueprintMetadata::MD_WorldContext))
{
FString const WorldContextPinName = Function->GetMetaData(FBlueprintMetadata::MD_WorldContext);
if (UEdGraphPin* ContextPin = FunctionNode->FindPin(WorldContextPinName))
{
if (UObjectPropertyBase* ContextProperty = Cast<UObjectPropertyBase>(ClassDebugData.FindClassPropertyForPin(ContextPin)))
{
UObject* PropertyValue = ContextProperty->GetObjectPropertyValue_InContainer(ActiveObjectBeingDebugged);
if (PropertyValue != NULL)
{
NodeWorldContext = PropertyValue;
}
}
}
}
LatentContextObjects.Add(NodeWorldContext);
}
for (UObject* ContextObject : LatentContextObjects)
{
if (UWorld* World = GEngine->GetWorldFromContextObject(ContextObject, /*bChecked =*/false))
{
FLatentActionManager& Manager = World->GetLatentActionManager();
TSet<int32> UUIDSet;
Manager.GetActiveUUIDs(ActiveObjectBeingDebugged, /*out*/ UUIDSet);
for (TSet<int32>::TConstIterator IterUUID(UUIDSet); IterUUID; ++IterUUID)
{
const int32 UUID = *IterUUID;
if (UEdGraphNode* ParentNode = ClassDebugData.FindNodeFromUUID(UUID))
{
TArray<FObjectUUIDPair>& Pairs = NodesWithActiveLatentActions.FindOrAdd(ParentNode);
new (Pairs) FObjectUUIDPair(ContextObject, UUID);
}
}
}
}
}
// Covert the watched pin array into a set
for (auto WatchedPinIt = SourceBlueprint->PinWatches.CreateConstIterator(); WatchedPinIt; ++WatchedPinIt)
{
UEdGraphPin* WatchedPin = *WatchedPinIt;
UEdGraphNode* OwningNode = Cast<UEdGraphNode>(WatchedPin->GetOuter());
if (!ensure(OwningNode != NULL)) // shouldn't happen, but just in case a dead pin was added to the PinWatches array
{
continue;
}
check(OwningNode == WatchedPin->GetOwningNode());
WatchedPinSet.Add(WatchedPin);
WatchedNodeSet.Add(OwningNode);
}
}
}
/////////////////////////////////////////////////////
// FConnectionDrawingPolicy
FConnectionDrawingPolicy::FConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements)
: WireLayerID(InBackLayerID)
, ArrowLayerID(InFrontLayerID)
, Settings(GetDefault<UGraphEditorSettings>())
, ZoomFactor(InZoomFactor)
, ClippingRect(InClippingRect)
, DrawElementsList(InDrawElements)
{
ArrowImage = FEditorStyle::GetBrush( TEXT("Graph.Arrow") );
ArrowRadius = ArrowImage->ImageSize * ZoomFactor * 0.5f;
MidpointImage = NULL;
MidpointRadius = FVector2D::ZeroVector;
HoverDeemphasisDarkFraction = 0.8f;
BubbleImage = FEditorStyle::GetBrush( TEXT("Graph.ExecutionBubble") );
}
void FConnectionDrawingPolicy::DrawSplineWithArrow(const FVector2D& StartPoint, const FVector2D& EndPoint, const FLinearColor& WireColor, float WireThickness, bool bDrawBubbles, bool Bidirectional)
{
// Draw the spline
DrawConnection(
WireLayerID,
StartPoint,
EndPoint,
WireColor,
WireThickness,
bDrawBubbles);
// Draw the arrow
if (ArrowImage != nullptr)
{
FVector2D ArrowPoint = EndPoint - ArrowRadius;
FSlateDrawElement::MakeBox(
DrawElementsList,
ArrowLayerID,
FPaintGeometry(ArrowPoint, ArrowImage->ImageSize * ZoomFactor, ZoomFactor),
ArrowImage,
ClippingRect,
ESlateDrawEffect::None,
WireColor
);
}
}
void FConnectionDrawingPolicy::DrawSplineWithArrow(FGeometry& StartGeom, FGeometry& EndGeom, const FLinearColor& WireColor, float WireThickness, bool bDrawBubbles, bool Bidirectional)
{
//@TODO: These values should be pushed into the Slate style, they are compensating for a bit of
// empty space inside of the pin brush images.
const float StartFudgeX = 4.0f;
const float EndFudgeX = 4.0f;
const FVector2D StartPoint = FGeometryHelper::VerticalMiddleRightOf(StartGeom) - FVector2D(StartFudgeX, 0.0f);
const FVector2D EndPoint = FGeometryHelper::VerticalMiddleLeftOf(EndGeom) - FVector2D(ArrowRadius.X - EndFudgeX, 0);
DrawSplineWithArrow(StartPoint, EndPoint, WireColor, WireThickness, bDrawBubbles, Bidirectional);
}
// Update the drawing policy with the set of hovered pins (which can be empty)
void FConnectionDrawingPolicy::SetHoveredPins(const TSet< TWeakObjectPtr<UEdGraphPin> >& InHoveredPins, const TArray< TSharedPtr<SGraphPin> >& OverridePins, double HoverTime)
{
HoveredPins.Empty();
LastHoverTimeEvent = (OverridePins.Num() > 0) ? 0.0 : HoverTime;
for (auto PinIt = OverridePins.CreateConstIterator(); PinIt; ++PinIt)
{
if (SGraphPin* GraphPin = PinIt->Get())
{
HoveredPins.Add(GraphPin->GetPinObj());
}
}
// Convert the widget pointer for hovered pins to be EdGraphPin pointers for their connected nets (both ends of any connection)
for (auto PinIt = InHoveredPins.CreateConstIterator(); PinIt; ++PinIt)
{
if (UEdGraphPin* Pin = PinIt->Get())
{
if (Pin->LinkedTo.Num() > 0)
{
HoveredPins.Add(Pin);
for (auto LinkIt = Pin->LinkedTo.CreateConstIterator(); LinkIt; ++LinkIt)
{
HoveredPins.Add(*LinkIt);
}
}
}
}
}
void FConnectionDrawingPolicy::SetMarkedPin(TWeakPtr<SGraphPin> InMarkedPin)
{
if (InMarkedPin.IsValid())
{
LastHoverTimeEvent = 0.0;
UEdGraphPin* MarkedPin = InMarkedPin.Pin()->GetPinObj();
HoveredPins.Add(MarkedPin);
for (auto LinkIt = MarkedPin->LinkedTo.CreateConstIterator(); LinkIt; ++LinkIt)
{
HoveredPins.Add(*LinkIt);
}
}
}
/** Util to make a 'distance->alpha' table and also return spline length */
float FConnectionDrawingPolicy::MakeSplineReparamTable(const FVector2D& P0, const FVector2D& P0Tangent, const FVector2D& P1, const FVector2D& P1Tangent, FInterpCurve<float>& OutReparamTable)
{
OutReparamTable.Reset();
const int32 NumSteps = 10; // TODO: Make this adaptive...
// Find range of input
float Param = 0.f;
const float MaxInput = 1.f;
const float Interval = (MaxInput - Param)/((float)(NumSteps-1));
// Add first entry, using first point on curve, total distance will be 0
FVector2D OldSplinePos = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, Param);
float TotalDist = 0.f;
OutReparamTable.AddPoint(TotalDist, Param);
Param += Interval;
// Then work over rest of points
for(int32 i=1; i<NumSteps; i++)
{
// Iterate along spline at regular param intervals
const FVector2D NewSplinePos = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, Param);
TotalDist += (NewSplinePos - OldSplinePos).Size();
OldSplinePos = NewSplinePos;
OutReparamTable.AddPoint(TotalDist, Param);
Param += Interval;
}
return TotalDist;
}
void FConnectionDrawingPolicy::DrawConnection( int32 LayerId, const FVector2D& Start, const FVector2D& End, const FLinearColor& InColor, float Thickness, bool bDrawBubbles )
{
const FVector2D& P0 = Start;
const FVector2D& P1 = End;
const FVector2D P0Tangent = Settings->ComputeSplineTangent(P0, P1);
const FVector2D P1Tangent = P0Tangent;
// Draw the spline itself
FSlateDrawElement::MakeDrawSpaceSpline(
DrawElementsList,
LayerId,
P0, P0Tangent,
P1, P1Tangent,
ClippingRect,
Thickness,
ESlateDrawEffect::None,
InColor
);
if (bDrawBubbles || (MidpointImage != NULL))
{
// This table maps distance along curve to alpha
FInterpCurve<float> SplineReparamTable;
float SplineLength = MakeSplineReparamTable(P0, P0Tangent, P1, P1Tangent, SplineReparamTable);
// Draw bubbles on the spline
if (bDrawBubbles)
{
const float BubbleSpacing = 64.f * ZoomFactor;
const float BubbleSpeed = 192.f * ZoomFactor;
const FVector2D BubbleSize = BubbleImage->ImageSize * ZoomFactor * 0.1f * Thickness;
float Time = (FPlatformTime::Seconds() - GStartTime);
const float BubbleOffset = FMath::Fmod(Time * BubbleSpeed, BubbleSpacing);
const int32 NumBubbles = FMath::CeilToInt(SplineLength/BubbleSpacing);
for (int32 i = 0; i < NumBubbles; ++i)
{
const float Distance = ((float)i * BubbleSpacing) + BubbleOffset;
if (Distance < SplineLength)
{
const float Alpha = SplineReparamTable.Eval(Distance, 0.f);
FVector2D BubblePos = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, Alpha);
BubblePos -= (BubbleSize * 0.5f);
FSlateDrawElement::MakeBox(
DrawElementsList,
LayerId,
FPaintGeometry( BubblePos, BubbleSize, ZoomFactor ),
BubbleImage,
ClippingRect,
ESlateDrawEffect::None,
InColor
);
}
}
}
// Draw the midpoint image
if (MidpointImage != NULL)
{
// Determine the spline position for the midpoint
const float MidpointAlpha = SplineReparamTable.Eval(SplineLength * 0.5f, 0.f);
const FVector2D Midpoint = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, MidpointAlpha);
// Approximate the slope at the midpoint (to orient the midpoint image to the spline)
const FVector2D MidpointPlusE = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, MidpointAlpha + KINDA_SMALL_NUMBER);
const FVector2D MidpointMinusE = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, MidpointAlpha - KINDA_SMALL_NUMBER);
const FVector2D SlopeUnnormalized = MidpointPlusE - MidpointMinusE;
// Draw the arrow
const FVector2D MidpointDrawPos = Midpoint - MidpointRadius;
const float AngleInRadians = SlopeUnnormalized.IsNearlyZero() ? 0.0f : FMath::Atan2(SlopeUnnormalized.Y, SlopeUnnormalized.X);
FSlateDrawElement::MakeRotatedBox(
DrawElementsList,
LayerId,
FPaintGeometry(MidpointDrawPos, MidpointImage->ImageSize * ZoomFactor, ZoomFactor),
MidpointImage,
ClippingRect,
ESlateDrawEffect::None,
AngleInRadians,
TOptional<FVector2D>(),
FSlateDrawElement::RelativeToElement,
InColor
);
}
}
}
void FConnectionDrawingPolicy::DrawPreviewConnector(const FGeometry& PinGeometry, const FVector2D& StartPoint, const FVector2D& EndPoint, UEdGraphPin* Pin)
{
float Thickness = 1.0f;
FLinearColor WireColor = FLinearColor::White;
bool bDrawBubbles = false;
bool bBiDirectional = false;
DetermineWiringStyle(Pin, NULL, /*inout*/ Thickness, /*inout*/ WireColor, /*inout*/ bDrawBubbles, /*inout*/ bBiDirectional);
DrawSplineWithArrow(StartPoint, EndPoint, WireColor, Thickness, bDrawBubbles, bBiDirectional);
}
void FConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ float& Thickness, /*inout*/ FLinearColor& WireColor, /*inout*/bool& bDrawBubbles, /*inout*/ bool &bBidirectional)
{
}
void FConnectionDrawingPolicy::DetermineLinkGeometry(
TMap<TSharedRef<SWidget>,
FArrangedWidget>& PinGeometries,
FArrangedChildren& ArrangedNodes,
TSharedRef<SWidget>& OutputPinWidget,
UEdGraphPin* OutputPin,
UEdGraphPin* InputPin,
/*out*/ FArrangedWidget*& StartWidgetGeometry,
/*out*/ FArrangedWidget*& EndWidgetGeometry
)
{
StartWidgetGeometry = PinGeometries.Find(OutputPinWidget);
if (TSharedRef<SGraphPin>* pTargetWidget = PinToPinWidgetMap.Find(InputPin))
{
TSharedRef<SGraphPin> InputWidget = *pTargetWidget;
EndWidgetGeometry = PinGeometries.Find(InputWidget);
}
}
void FConnectionDrawingPolicy::Draw(TMap<TSharedRef<SWidget>, FArrangedWidget>& PinGeometries, FArrangedChildren& ArrangedNodes)
{
PinToPinWidgetMap.Empty();
for (TMap<TSharedRef<SWidget>, FArrangedWidget>::TIterator ConnectorIt(PinGeometries); ConnectorIt; ++ConnectorIt)
{
TSharedRef<SWidget> SomePinWidget = ConnectorIt.Key();
SGraphPin& PinWidget = static_cast<SGraphPin&>(SomePinWidget.Get());
PinToPinWidgetMap.Add(PinWidget.GetPinObj(), StaticCastSharedRef<SGraphPin>(SomePinWidget));
}
for (TMap<TSharedRef<SWidget>, FArrangedWidget>::TIterator ConnectorIt(PinGeometries); ConnectorIt; ++ConnectorIt)
{
TSharedRef<SWidget> SomePinWidget = ConnectorIt.Key();
SGraphPin& PinWidget = static_cast<SGraphPin&>(SomePinWidget.Get());
UEdGraphPin* ThePin = PinWidget.GetPinObj();
if (ThePin->Direction == EGPD_Output)
{
for (int32 LinkIndex=0; LinkIndex < ThePin->LinkedTo.Num(); ++LinkIndex)
{
FArrangedWidget* LinkStartWidgetGeometry = NULL;
FArrangedWidget* LinkEndWidgetGeometry = NULL;
UEdGraphPin* TargetPin = ThePin->LinkedTo[LinkIndex];
DetermineLinkGeometry(PinGeometries, ArrangedNodes, SomePinWidget, ThePin, TargetPin, /*out*/ LinkStartWidgetGeometry, /*out*/ LinkEndWidgetGeometry);
if(( LinkEndWidgetGeometry && LinkStartWidgetGeometry ) && !IsConnectionCulled( *LinkStartWidgetGeometry, *LinkEndWidgetGeometry ))
{
float Thickness = 1.0f;
FLinearColor WireColor = FLinearColor::White;
bool bDrawBubbles = false;
bool bBidirectional = false;
DetermineWiringStyle(ThePin, TargetPin, /*inout*/ Thickness, /*inout*/ WireColor, /*inout*/ bDrawBubbles, /*inout*/ bBidirectional);
DrawSplineWithArrow(LinkStartWidgetGeometry->Geometry, LinkEndWidgetGeometry->Geometry, WireColor, Thickness, bDrawBubbles, bBidirectional);
}
}
}
}
}
bool FConnectionDrawingPolicy::IsConnectionCulled( const FArrangedWidget& StartLink, const FArrangedWidget& EndLink ) const
{
const float Top = FMath::Min( StartLink.Geometry.AbsolutePosition.Y, EndLink.Geometry.AbsolutePosition.Y );
const float Left = FMath::Min( StartLink.Geometry.AbsolutePosition.X, EndLink.Geometry.AbsolutePosition.X );
const float Bottom = FMath::Max( StartLink.Geometry.AbsolutePosition.Y, EndLink.Geometry.AbsolutePosition.Y );
const float Right = FMath::Max( StartLink.Geometry.AbsolutePosition.X, EndLink.Geometry.AbsolutePosition.X );
return Left > ClippingRect.Right || Right < ClippingRect.Left ||
Bottom < ClippingRect.Top || Top > ClippingRect.Bottom;
}
void FConnectionDrawingPolicy::SetIncompatiblePinDrawState(const TSharedPtr<SGraphPin>& StartPin, const TSet< TSharedRef<SWidget> >& VisiblePins)
{
}
void FConnectionDrawingPolicy::ResetIncompatiblePinDrawState(const TSet< TSharedRef<SWidget> >& VisiblePins)
{
}
void FConnectionDrawingPolicy::ApplyHoverDeemphasis(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ float& Thickness, /*inout*/ FLinearColor& WireColor)
{
const float FadeInBias = 0.75f; // Time in seconds before the fading starts to occur
const float FadeInPeriod = 0.6f; // Time in seconds after the bias before the fade is fully complete
const float TimeFraction = FMath::SmoothStep(0.0f, FadeInPeriod, (float)(FSlateApplication::Get().GetCurrentTime() - LastHoverTimeEvent - FadeInBias));
const float LightFraction = 0.25f;
const FLinearColor DarkenedColor(0.0f, 0.0f, 0.0f, 0.5f);
const FLinearColor LightenedColor(1.0f, 1.0f, 1.0f, 1.0f);
const bool bContainsBoth = HoveredPins.Contains(InputPin) && HoveredPins.Contains(OutputPin);
const bool bContainsOutput = HoveredPins.Contains(OutputPin);
const bool bEmphasize = bContainsBoth || (bContainsOutput && (InputPin == NULL));
if (bEmphasize)
{
Thickness = FMath::Lerp(Thickness, Thickness * ((Thickness < 3.0f) ? 5.0f : 3.0f), TimeFraction);
WireColor = FMath::Lerp<FLinearColor>(WireColor, LightenedColor, LightFraction * TimeFraction);
}
else
{
WireColor = FMath::Lerp<FLinearColor>(WireColor, DarkenedColor, HoverDeemphasisDarkFraction * TimeFraction);
}
}
/////////////////////////////////////////////////////
// FKismetConnectionDrawingPolicy
FKismetConnectionDrawingPolicy::FKismetConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements, UEdGraph* InGraphObj)
: FConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, ZoomFactor, InClippingRect, InDrawElements)
, GraphObj(InGraphObj)
{
// Don't want to draw ending arrowheads
ArrowImage = nullptr;
ArrowRadius = FVector2D::ZeroVector;
// But we do want to draw midpoint arrowheads
if (GetDefault<UBlueprintEditorSettings>()->bDrawMidpointArrowsInBlueprints)
{
MidpointImage = FEditorStyle::GetBrush( TEXT("Graph.Arrow") );
MidpointRadius = MidpointImage->ImageSize * ZoomFactor * 0.5f;
}
// Cache off the editor options
AttackColor = Settings->TraceAttackColor;
SustainColor = Settings->TraceSustainColor;
ReleaseColor = Settings->TraceReleaseColor;
AttackWireThickness = Settings->TraceAttackWireThickness;
SustainWireThickness = Settings->TraceSustainWireThickness;
ReleaseWireThickness = Settings->TraceReleaseWireThickness;
TracePositionBonusPeriod = Settings->TracePositionBonusPeriod;
TracePositionExponent = Settings->TracePositionExponent;
AttackHoldPeriod = Settings->TraceAttackHoldPeriod;
DecayPeriod = Settings->TraceDecayPeriod;
DecayExponent = Settings->TraceDecayExponent;
SustainHoldPeriod = Settings->TraceSustainHoldPeriod;
ReleasePeriod = Settings->TraceReleasePeriod;
ReleaseExponent = Settings->TraceReleaseExponent;
CurrentTime = 0.0;
LatestTimeDiscovered = 0.0;
}
void FKismetConnectionDrawingPolicy::Draw(TMap<TSharedRef<SWidget>, FArrangedWidget>& PinGeometries, FArrangedChildren& ArrangedNodes)
{
// Build the execution roadmap (also populates execution times)
BuildExecutionRoadmap();
// Draw everything
FConnectionDrawingPolicy::Draw(PinGeometries, ArrangedNodes);
}
bool FKismetConnectionDrawingPolicy::CanBuildRoadmap() const
{
UBlueprint* TargetBP = FBlueprintEditorUtils::FindBlueprintForGraphChecked(GraphObj);
UObject* ActiveObject = TargetBP->GetObjectBeingDebugged();
return ActiveObject != NULL;
}
void FKismetConnectionDrawingPolicy::BuildExecutionRoadmap()
{
LatestTimeDiscovered = 0.0;
// Only do highlighting in PIE or SIE
if (!CanBuildRoadmap())
{
return;
}
UBlueprint* TargetBP = FBlueprintEditorUtils::FindBlueprintForGraphChecked(GraphObj);
UObject* ActiveObject = TargetBP->GetObjectBeingDebugged();
check(ActiveObject); // Due to CanBuildRoadmap
// Redirect the target Blueprint when debugging with a macro graph visible
if (TargetBP->BlueprintType == BPTYPE_MacroLibrary)
{
TargetBP = Cast<UBlueprint>(ActiveObject->GetClass()->ClassGeneratedBy);
}
TArray<UEdGraphNode*> SequentialNodesInGraph;
TArray<double> SequentialNodeTimes;
TArray<UEdGraphPin*> SequentialExecPinsInGraph;
{
const TSimpleRingBuffer<FKismetTraceSample>& TraceStack = FKismetDebugUtilities::GetTraceStack();
UBlueprintGeneratedClass* TargetClass = Cast<UBlueprintGeneratedClass>(TargetBP->GeneratedClass);
FBlueprintDebugData& DebugData = TargetClass->GetDebugData();
for (int32 i = 0; i < TraceStack.Num(); ++i)
{
const FKismetTraceSample& Sample = TraceStack(i);
if (UObject* TestObject = Sample.Context.Get())
{
if (TestObject == ActiveObject)
{
UEdGraphPin* AssociatedPin = DebugData.FindExecPinFromCodeLocation(Sample.Function.Get(), Sample.Offset);
if (UEdGraphNode* Node = DebugData.FindSourceNodeFromCodeLocation(Sample.Function.Get(), Sample.Offset, /*bAllowImpreciseHit=*/ false))
{
if (GraphObj == Node->GetGraph())
{
SequentialNodesInGraph.Add(Node);
SequentialNodeTimes.Add(Sample.ObservationTime);
SequentialExecPinsInGraph.Add(AssociatedPin);
}
else
{
// If the top-level source node is a macro instance node
UK2Node_MacroInstance* MacroInstanceNode = Cast<UK2Node_MacroInstance>(Node);
if (MacroInstanceNode)
{
// Attempt to locate the macro source node through the code mapping
UEdGraphNode* MacroSourceNode = DebugData.FindMacroSourceNodeFromCodeLocation(Sample.Function.Get(), Sample.Offset);
if (MacroSourceNode)
{
// If the macro source node is located in the current graph context
if (GraphObj == MacroSourceNode->GetGraph())
{
// Add it to the sequential node list
SequentialNodesInGraph.Add(MacroSourceNode);
SequentialNodeTimes.Add(Sample.ObservationTime);
SequentialExecPinsInGraph.Add(AssociatedPin);
}
else
{
// The macro source node isn't in the current graph context, but we might have a macro instance node that is
// in the current graph context, so obtain the set of macro instance nodes that are mapped to the code here.
TArray<UEdGraphNode*> MacroInstanceNodes;
DebugData.FindMacroInstanceNodesFromCodeLocation(Sample.Function.Get(), Sample.Offset, MacroInstanceNodes);
// For each macro instance node in the set
for (auto MacroInstanceNodeIt = MacroInstanceNodes.CreateConstIterator(); MacroInstanceNodeIt; ++MacroInstanceNodeIt)
{
// If the macro instance node is located in the current graph context
MacroInstanceNode = Cast<UK2Node_MacroInstance>(*MacroInstanceNodeIt);
if (MacroInstanceNode && GraphObj == MacroInstanceNode->GetGraph())
{
// Add it to the sequential node list
SequentialNodesInGraph.Add(MacroInstanceNode);
SequentialNodeTimes.Add(Sample.ObservationTime);
SequentialExecPinsInGraph.Add(AssociatedPin);
// Exit the loop; we're done
break;
}
}
}
}
}
}
}
}
}
}
}
// Run thru and apply bonus time
const float InvNumNodes = 1.0f / (float)SequentialNodeTimes.Num();
for (int32 i = 0; i < SequentialNodesInGraph.Num(); ++i)
{
double& ObservationTime = SequentialNodeTimes[i];
const float PositionRatio = (SequentialNodeTimes.Num() - i) * InvNumNodes;
const float PositionBonus = FMath::Pow(PositionRatio, TracePositionExponent) * TracePositionBonusPeriod;
ObservationTime += PositionBonus;
LatestTimeDiscovered = FMath::Max<double>(LatestTimeDiscovered, ObservationTime);
}
UEdGraphPin* LastExecPin = NULL;
// Record the unique exec-pin to time pairings, keeping only the most recent
// times for each pairing... reverse the "SequentialNodes" because right now
// it is in stack order (with the last executed node first)
for (int32 i = SequentialNodesInGraph.Num() - 1; i >= 1; --i)
{
UEdGraphNode* CurNode = SequentialNodesInGraph[i];
UEdGraphNode* NextNode = SequentialNodesInGraph[i-1];
// keep track of the last exec-pin executed by CurNode (these tracked
// pins coincide with "WireTraceSite" op-codes that have been injected
// prior to every "goto" statement... this way we have context for which
// pin executed the jump)
if (UEdGraphPin* AssociatedPin = SequentialExecPinsInGraph[i])
{
LastExecPin = AssociatedPin;
}
// if this statement is a jump (from one node to another)
if (CurNode != NextNode)
{
// if there was a wire-trace op-code inserted before this jump
if (LastExecPin != NULL)
{
//ensure(LastExecPin->GetOwningNode() == CurNode);
double NextNodeTime = SequentialNodeTimes[i-1];
FExecPairingMap& ExecPaths = PredecessorPins.FindOrAdd(NextNode);
FTimePair& ExecTiming = ExecPaths.FindOrAdd(LastExecPin);
// make sure that if we've already visited this exec-pin (like
// in a for-loop or something), that we're replacing it with a
// more recent execution time
//
// @TODO I don't see when this wouldn't be the case
if (ExecTiming.ThisExecTime < NextNodeTime)
{
double CurNodeTime = SequentialNodeTimes[i];
ExecTiming.ThisExecTime = NextNodeTime;
ExecTiming.PredExecTime = CurNodeTime;
}
}
// if the nodes aren't graphically connected how could they be
// executed back-to-back? well, this could be a pop back to a
// sequence node from the end of one thread of execution, etc.
else if (AreNodesGraphicallySequential(CurNode, NextNode))
{
// only warn when the nodes are directly connected (this is all
// for execution flow visualization after all)
UE_LOG(LogConnectionDrawingPolicy, Warning, TEXT("Looks like a wire-trace was not injected before the jump from '%s' to '%s'."),
*CurNode->GetNodeTitle(ENodeTitleType::FullTitle).ToString(), *NextNode->GetNodeTitle(ENodeTitleType::FullTitle).ToString());
}
// clear the exec-pin (we're moving to a new node and want to find
// it's executed out pin)
LastExecPin = NULL;
}
// else, we're only collecting this data for tracing node-to-node
// executions (so we don't care about this sequence of statements)
}
// Fade only when free-running (since we're using FApp::GetCurrentTime(), instead of FPlatformTime::Seconds)
const double MaxTimeAhead = FMath::Min(FApp::GetCurrentTime() + 2*TracePositionBonusPeriod, LatestTimeDiscovered); //@TODO: Rough clamping; should be exposed as a parameter
CurrentTime = FMath::Max(FApp::GetCurrentTime(), MaxTimeAhead);
}
void FKismetConnectionDrawingPolicy::CalculateEnvelopeAlphas(double ExecutionTime, /*out*/ float& AttackAlpha, /*out*/ float& SustainAlpha) const
{
const float DeltaTime = (float)(CurrentTime - ExecutionTime);
{
const float UnclampedDecayRatio = 1.0f - ((DeltaTime - AttackHoldPeriod) / DecayPeriod);
const float ClampedDecayRatio = FMath::Clamp<float>(UnclampedDecayRatio, 0.0f, 1.0f);
AttackAlpha = FMath::Pow(ClampedDecayRatio, DecayExponent);
}
{
const float SustainEndTime = AttackHoldPeriod + DecayPeriod + SustainHoldPeriod;
const float UnclampedReleaseRatio = 1.0f - ((DeltaTime - SustainEndTime) / ReleasePeriod);
const float ClampedReleaseRatio = FMath::Clamp<float>(UnclampedReleaseRatio, 0.0f, 1.0f);
SustainAlpha = FMath::Pow(ClampedReleaseRatio, ReleaseExponent);
}
}
bool FKismetConnectionDrawingPolicy::TreatWireAsExecutionPin(UEdGraphPin* InputPin, UEdGraphPin* OutputPin) const
{
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
return (InputPin != NULL) && (Schema->IsExecPin(*OutputPin));
}
bool FKismetConnectionDrawingPolicy::AreNodesGraphicallySequential(UEdGraphNode* InputNode, UEdGraphNode* OutputNode) const
{
for (UEdGraphPin* Pin : InputNode->Pins)
{
if (Pin->Direction != EGPD_Output)
{
continue;
}
for (UEdGraphPin* Connection : Pin->LinkedTo)
{
if (!TreatWireAsExecutionPin(Pin, Connection))
{
continue;
}
if (Connection->GetOwningNode() == OutputNode)
{
return true;
}
}
}
return false;
}
void FKismetConnectionDrawingPolicy::DetermineStyleOfExecWire(float& Thickness, FLinearColor& WireColor, bool& bDrawBubbles, const FTimePair& Times)
{
// It's a followed link, make it strong and yellowish but fading over time
const double ExecTime = Times.ThisExecTime;
float AttackAlpha;
float SustainAlpha;
CalculateEnvelopeAlphas(ExecTime, /*out*/ AttackAlpha, /*out*/ SustainAlpha);
const float DecayedAttackThickness = FMath::Lerp<float>(SustainWireThickness, AttackWireThickness, AttackAlpha);
Thickness = FMath::Lerp<float>(ReleaseWireThickness, DecayedAttackThickness, SustainAlpha);
const FLinearColor DecayedAttackColor = FMath::Lerp<FLinearColor>(SustainColor, AttackColor, AttackAlpha);
WireColor = WireColor * FMath::Lerp<FLinearColor>(ReleaseColor, DecayedAttackColor, SustainAlpha);
if (SustainAlpha > KINDA_SMALL_NUMBER)
{
bDrawBubbles = true;
}
}
FKismetConnectionDrawingPolicy::FTimePair const* FKismetConnectionDrawingPolicy::BackTraceExecPath(UEdGraphPin const* const OutputPin, FExecPairingMap const* const NodeExecutionList)
{
FTimePair const* FoundExecPath = nullptr;
UEdGraphNode const* const OwningNode = OutputPin->GetOwningNode();
if (UK2Node_Knot const* const KnotNode = Cast<UK2Node_Knot>(OwningNode))
{
UEdGraphPin const* const KnotInputPin = KnotNode->GetInputPin();
for (UEdGraphPin const* KnotInput : KnotInputPin->LinkedTo)
{
FoundExecPath = BackTraceExecPath(KnotInput, NodeExecutionList);
if (FoundExecPath != nullptr)
{
break;
}
}
}
else
{
FoundExecPath = NodeExecutionList->Find(OutputPin);
}
return FoundExecPath;
}
// Give specific editor modes a chance to highlight this connection or darken non-interesting connections
void FKismetConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ float& Thickness, /*inout*/ FLinearColor& WireColor, /*inout*/bool& bDrawBubbles, /*inout*/bool& bBidirectional)
{
// Get the schema and grab the default color from it
check(OutputPin);
check(GraphObj);
const UEdGraphSchema* Schema = GraphObj->GetSchema();
WireColor = Schema->GetPinTypeColor(OutputPin->PinType);
const bool bDeemphasizeUnhoveredPins = HoveredPins.Num() > 0;
// If this is a K2 graph, try to be a little more specific
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(Schema);
if (K2Schema != NULL)
{
if (TreatWireAsExecutionPin(InputPin, OutputPin))
{
if (CanBuildRoadmap())
{
UEdGraphNode* InputNode = InputPin->GetOwningNode();
// knot nodes are removed from the graph at compile time, so we
// have to follow them until we find something that would have
// actually executed
while (UK2Node_Knot* KnotNode = Cast<UK2Node_Knot>(InputNode))
{
InputNode = nullptr;
UEdGraphPin* OutPin = KnotNode->GetOutputPin();
if (OutPin->LinkedTo.Num() > 0)
{
check(OutPin->LinkedTo.Num() == 1);
InputNode = OutPin->LinkedTo[0]->GetOwningNode();
}
}
// track if this node connection was ran or not
bool bExecuted = false;
// if the node belonging to InputPin was actually executed
if (FExecPairingMap* ExecPaths = PredecessorPins.Find(InputNode))
{
// if the output pin is one of the pins that lead to InputNode being ran
if (FTimePair const* ExecTiming = BackTraceExecPath(OutputPin, ExecPaths))
{
bExecuted = true;
DetermineStyleOfExecWire(/*inout*/ Thickness, /*inout*/ WireColor, /*inout*/ bDrawBubbles, *ExecTiming);
}
}
if (!bExecuted)
{
// It's not followed, fade it and keep it thin
WireColor = ReleaseColor;
Thickness = ReleaseWireThickness;
}
}
else
{
// Make exec wires slightly thicker even outside of debug
Thickness = 3.0f;
}
}
else
{
// Array types should draw thicker
if( (InputPin && InputPin->PinType.bIsArray) || (OutputPin && OutputPin->PinType.bIsArray) )
{
Thickness = 3.0f;
}
}
}
if (bDeemphasizeUnhoveredPins)
{
ApplyHoverDeemphasis(OutputPin, InputPin, /*inout*/ Thickness, /*inout*/ WireColor);
}
}
void FKismetConnectionDrawingPolicy::SetIncompatiblePinDrawState(const TSharedPtr<SGraphPin>& StartPin, const TSet< TSharedRef<SWidget> >& VisiblePins)
{
ResetIncompatiblePinDrawState(VisiblePins);
for(auto VisiblePinIterator = VisiblePins.CreateConstIterator(); VisiblePinIterator; ++VisiblePinIterator)
{
TSharedPtr<SGraphPin> CheckPin = StaticCastSharedRef<SGraphPin>(*VisiblePinIterator);
if(CheckPin != StartPin)
{
const FPinConnectionResponse Response = StartPin->GetPinObj()->GetSchema()->CanCreateConnection(StartPin->GetPinObj(), CheckPin->GetPinObj());
if(Response.Response == CONNECT_RESPONSE_DISALLOW)
{
CheckPin->SetPinColorModifier(FLinearColor(0.25f, 0.25f, 0.25f, 0.5f));
}
}
}
}
void FKismetConnectionDrawingPolicy::ResetIncompatiblePinDrawState(const TSet< TSharedRef<SWidget> >& VisiblePins)
{
for(auto VisiblePinIterator = VisiblePins.CreateConstIterator(); VisiblePinIterator; ++VisiblePinIterator)
{
TSharedPtr<SGraphPin> VisiblePin = StaticCastSharedRef<SGraphPin>(*VisiblePinIterator);
VisiblePin->SetPinColorModifier(FLinearColor::White);
}
}
/////////////////////////////////////////////////////
// FStateMachineConnectionDrawingPolicy
FStateMachineConnectionDrawingPolicy::FStateMachineConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements, UEdGraph* InGraphObj)
: FConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, ZoomFactor, InClippingRect, InDrawElements)
, GraphObj(InGraphObj)
{
}
void FStateMachineConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ float& Thickness, /*inout*/ FLinearColor& WireColor, /*inout*/ bool& bDrawBubbles, /*inout*/ bool& bBidirectional)
{
Thickness = 1.5f;
if (InputPin)
{
if (UAnimStateTransitionNode* TransNode = Cast<UAnimStateTransitionNode>(InputPin->GetOwningNode()))
{
WireColor = SGraphNodeAnimTransition::StaticGetTransitionColor(TransNode, HoveredPins.Contains(InputPin));
bBidirectional = TransNode->Bidirectional;
}
}
const bool bDeemphasizeUnhoveredPins = HoveredPins.Num() > 0;
if (bDeemphasizeUnhoveredPins)
{
ApplyHoverDeemphasis(OutputPin, InputPin, /*inout*/ Thickness, /*inout*/ WireColor);
}
}
void FStateMachineConnectionDrawingPolicy::DetermineLinkGeometry(
TMap<TSharedRef<SWidget>, FArrangedWidget>& PinGeometries,
FArrangedChildren& ArrangedNodes,
TSharedRef<SWidget>& OutputPinWidget,
UEdGraphPin* OutputPin,
UEdGraphPin* InputPin,
/*out*/ FArrangedWidget*& StartWidgetGeometry,
/*out*/ FArrangedWidget*& EndWidgetGeometry
)
{
if (UAnimStateEntryNode* EntryNode = Cast<UAnimStateEntryNode>(OutputPin->GetOwningNode()))
{
//FConnectionDrawingPolicy::DetermineLinkGeometry(PinGeometries, ArrangedNodes, OutputPinWidget, OutputPin, InputPin, StartWidgetGeometry, EndWidgetGeometry);
StartWidgetGeometry = PinGeometries.Find(OutputPinWidget);
UAnimStateNodeBase* State = CastChecked<UAnimStateNodeBase>(InputPin->GetOwningNode());
int32 StateIndex = NodeWidgetMap.FindChecked(State);
EndWidgetGeometry = &(ArrangedNodes[StateIndex]);
}
else if (UAnimStateTransitionNode* TransNode = Cast<UAnimStateTransitionNode>(InputPin->GetOwningNode()))
{
UAnimStateNodeBase* PrevState = TransNode->GetPreviousState();
UAnimStateNodeBase* NextState = TransNode->GetNextState();
if ((PrevState != NULL) && (NextState != NULL))
{
int32* PrevNodeIndex = NodeWidgetMap.Find(PrevState);
int32* NextNodeIndex = NodeWidgetMap.Find(NextState);
if ((PrevNodeIndex != NULL) && (NextNodeIndex != NULL))
{
StartWidgetGeometry = &(ArrangedNodes[*PrevNodeIndex]);
EndWidgetGeometry = &(ArrangedNodes[*NextNodeIndex]);
}
}
}
else
{
StartWidgetGeometry = PinGeometries.Find(OutputPinWidget);
if (TSharedRef<SGraphPin>* pTargetWidget = PinToPinWidgetMap.Find(InputPin))
{
TSharedRef<SGraphPin> InputWidget = *pTargetWidget;
EndWidgetGeometry = PinGeometries.Find(InputWidget);
}
}
}
void FStateMachineConnectionDrawingPolicy::Draw(TMap<TSharedRef<SWidget>, FArrangedWidget>& PinGeometries, FArrangedChildren& ArrangedNodes)
{
// Build an acceleration structure to quickly find geometry for the nodes
NodeWidgetMap.Empty();
for (int32 NodeIndex = 0; NodeIndex < ArrangedNodes.Num(); ++NodeIndex)
{
FArrangedWidget& CurWidget = ArrangedNodes[NodeIndex];
TSharedRef<SGraphNode> ChildNode = StaticCastSharedRef<SGraphNode>(CurWidget.Widget);
NodeWidgetMap.Add(ChildNode->GetNodeObj(), NodeIndex);
}
// Now draw
FConnectionDrawingPolicy::Draw(PinGeometries, ArrangedNodes);
}
void FStateMachineConnectionDrawingPolicy::DrawPreviewConnector(const FGeometry& PinGeometry, const FVector2D& StartPoint, const FVector2D& EndPoint, UEdGraphPin* Pin)
{
float Thickness = 1.0f;
FLinearColor WireColor = FLinearColor::White;
bool bDrawBubbles = false;
bool bBiDirectional = false;
DetermineWiringStyle(Pin, NULL, /*inout*/ Thickness, /*inout*/ WireColor, /*inout*/ bDrawBubbles, /*inout*/ bBiDirectional);
const FVector2D SeedPoint = EndPoint;
const FVector2D AdjustedStartPoint = FGeometryHelper::FindClosestPointOnGeom(PinGeometry, SeedPoint);
DrawSplineWithArrow(AdjustedStartPoint, EndPoint, WireColor, Thickness, bDrawBubbles, bBiDirectional);
}
void FStateMachineConnectionDrawingPolicy::DrawSplineWithArrow(const FVector2D& StartAnchorPoint, const FVector2D& EndAnchorPoint, const FLinearColor& WireColor, float WireThickness, bool bDrawBubbles, bool Bidirectional)
{
Internal_DrawLineWithArrow(StartAnchorPoint, EndAnchorPoint, WireColor, WireThickness, bDrawBubbles);
if (Bidirectional)
{
Internal_DrawLineWithArrow(EndAnchorPoint, StartAnchorPoint, WireColor, WireThickness, bDrawBubbles);
}
}
void FStateMachineConnectionDrawingPolicy::Internal_DrawLineWithArrow(const FVector2D& StartAnchorPoint, const FVector2D& EndAnchorPoint, const FLinearColor& WireColor, float WireThickness, bool bDrawBubbles)
{
//@TODO: Should this be scaled by zoom factor?
const float LineSeparationAmount = 4.5f;
const FVector2D DeltaPos = EndAnchorPoint - StartAnchorPoint;
const FVector2D UnitDelta = DeltaPos.SafeNormal();
const FVector2D Normal = FVector2D(DeltaPos.Y, -DeltaPos.X).SafeNormal();
// Come up with the final start/end points
const FVector2D DirectionBias = Normal * LineSeparationAmount;
const FVector2D LengthBias = ArrowRadius.X * UnitDelta;
const FVector2D StartPoint = StartAnchorPoint + DirectionBias + LengthBias;
const FVector2D EndPoint = EndAnchorPoint + DirectionBias - LengthBias;
// Draw a line/spline
DrawConnection(WireLayerID, StartPoint, EndPoint, WireColor, WireThickness, bDrawBubbles);
// Draw the arrow
const FVector2D ArrowDrawPos = EndPoint - ArrowRadius;
const float AngleInRadians = FMath::Atan2(DeltaPos.Y, DeltaPos.X);
FSlateDrawElement::MakeRotatedBox(
DrawElementsList,
ArrowLayerID,
FPaintGeometry(ArrowDrawPos, ArrowImage->ImageSize * ZoomFactor, ZoomFactor),
ArrowImage,
ClippingRect,
ESlateDrawEffect::None,
AngleInRadians,
TOptional<FVector2D>(),
FSlateDrawElement::RelativeToElement,
WireColor
);
}
void FStateMachineConnectionDrawingPolicy::DrawSplineWithArrow(FGeometry& StartGeom, FGeometry& EndGeom, const FLinearColor& WireColor, float WireThickness, bool bDrawBubbles, bool Bidirectional)
{
// Get a reasonable seed point (halfway between the boxes)
const FVector2D StartCenter = FGeometryHelper::CenterOf(StartGeom);
const FVector2D EndCenter = FGeometryHelper::CenterOf(EndGeom);
const FVector2D SeedPoint = (StartCenter + EndCenter) * 0.5f;
// Find the (approximate) closest points between the two boxes
const FVector2D StartAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(StartGeom, SeedPoint);
const FVector2D EndAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(EndGeom, SeedPoint);
DrawSplineWithArrow(StartAnchorPoint, EndAnchorPoint, WireColor, WireThickness, bDrawBubbles, Bidirectional);
}
void FStateMachineConnectionDrawingPolicy::DrawConnection(int32 LayerId, const FVector2D& Start, const FVector2D& End, const FLinearColor& InColor, float Thickness, bool bDrawBubbles)
{
const FVector2D& P0 = Start;
const FVector2D& P1 = End;
const FVector2D Delta = End-Start;
const FVector2D NormDelta = Delta.SafeNormal();
const FVector2D P0Tangent = NormDelta;
const FVector2D P1Tangent = NormDelta;
// Draw the spline itself
FSlateDrawElement::MakeDrawSpaceSpline(
DrawElementsList,
LayerId,
P0, P0Tangent,
P1, P1Tangent,
ClippingRect,
Thickness,
ESlateDrawEffect::None,
InColor
);
//@TODO: Handle bDrawBubbles
}
/////////////////////////////////////////////////////
// FAnimGraphConnectionDrawingPolicy
FAnimGraphConnectionDrawingPolicy::FAnimGraphConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements, UEdGraph* InGraphObj)
: FKismetConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, ZoomFactor, InClippingRect, InDrawElements, InGraphObj)
{
}
bool FAnimGraphConnectionDrawingPolicy::TreatWireAsExecutionPin(UEdGraphPin* InputPin, UEdGraphPin* OutputPin) const
{
const UAnimationGraphSchema* Schema = GetDefault<UAnimationGraphSchema>();
return (InputPin != NULL) && (Schema->IsPosePin(OutputPin->PinType));
}
void FAnimGraphConnectionDrawingPolicy::BuildExecutionRoadmap()
{
UAnimBlueprint* TargetBP = CastChecked<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForGraphChecked(GraphObj));
UAnimBlueprintGeneratedClass* AnimBlueprintClass = (UAnimBlueprintGeneratedClass*)(*(TargetBP->GeneratedClass));
if (TargetBP->GetObjectBeingDebugged() == NULL)
{
return;
}
TMap<UProperty*, UObject*> PropertySourceMap;
AnimBlueprintClass->GetDebugData().GenerateReversePropertyMap(/*out*/ PropertySourceMap);
FAnimBlueprintDebugData& DebugInfo = AnimBlueprintClass->GetAnimBlueprintDebugData();
for (auto VisitIt = DebugInfo.UpdatedNodesThisFrame.CreateIterator(); VisitIt; ++VisitIt)
{
const FAnimBlueprintDebugData::FNodeVisit& VisitRecord = *VisitIt;
if ((VisitRecord.SourceID >= 0) && (VisitRecord.SourceID < AnimBlueprintClass->AnimNodeProperties.Num()) && (VisitRecord.TargetID >= 0) && (VisitRecord.TargetID < AnimBlueprintClass->AnimNodeProperties.Num()))
{
if (UAnimGraphNode_Base* SourceNode = Cast<UAnimGraphNode_Base>(PropertySourceMap.FindRef(AnimBlueprintClass->AnimNodeProperties[VisitRecord.SourceID])))
{
if (UAnimGraphNode_Base* TargetNode = Cast<UAnimGraphNode_Base>(PropertySourceMap.FindRef(AnimBlueprintClass->AnimNodeProperties[VisitRecord.TargetID])))
{
UEdGraphPin* PoseNet = NULL;
UAnimationGraphSchema const* AnimSchema = GetDefault<UAnimationGraphSchema>();
for (int32 PinIndex = 0; PinIndex < TargetNode->Pins.Num(); ++PinIndex)
{
UEdGraphPin* Pin = TargetNode->Pins[PinIndex];
if (AnimSchema->IsPosePin(Pin->PinType) && (Pin->Direction == EGPD_Output))
{
PoseNet = Pin;
break;
}
}
if (PoseNet != NULL)
{
//@TODO: Extend the rendering code to allow using the recorded blend weight instead of faked exec times
FExecPairingMap& Predecessors = PredecessorPins.FindOrAdd((UEdGraphNode*)SourceNode);
FTimePair& Timings = Predecessors.FindOrAdd(PoseNet);
Timings.PredExecTime = 0.0;
Timings.ThisExecTime = VisitRecord.Weight;
}
}
}
}
}
}
void FAnimGraphConnectionDrawingPolicy::DetermineStyleOfExecWire(float& Thickness, FLinearColor& WireColor, bool& bDrawBubbles, const FTimePair& Times)
{
// It's a followed link, make it strong and yellowish but fading over time
const double BlendWeight = Times.ThisExecTime;
const float HeavyBlendThickness = AttackWireThickness;
const float LightBlendThickness = SustainWireThickness;
Thickness = FMath::Lerp<float>(LightBlendThickness, HeavyBlendThickness, BlendWeight);
WireColor = WireColor * (BlendWeight * 0.5f + 0.5f);//FMath::Lerp<FLinearColor>(SustainColor, AttackColor, BlendWeight);
bDrawBubbles = true;
}
/////////////////////////////////////////////////////
// FSoundCueGraphConnectionDrawingPolicy
FSoundCueGraphConnectionDrawingPolicy::FSoundCueGraphConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements, UEdGraph* InGraphObj)
: FConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, ZoomFactor, InClippingRect, InDrawElements)
, GraphObj(InGraphObj)
{
// Cache off the editor options
ActiveColor = Settings->TraceAttackColor;
InactiveColor = Settings->TraceReleaseColor;
ActiveWireThickness = Settings->TraceAttackWireThickness;
InactiveWireThickness = Settings->TraceReleaseWireThickness;
// Don't want to draw ending arrowheads
ArrowImage = nullptr;
ArrowRadius = FVector2D::ZeroVector;
}
void FSoundCueGraphConnectionDrawingPolicy::Draw(TMap<TSharedRef<SWidget>, FArrangedWidget>& PinGeometries, FArrangedChildren& ArrangedNodes)
{
// Build the execution roadmap (also populates execution times)
BuildAudioFlowRoadmap();
// Draw everything
FConnectionDrawingPolicy::Draw(PinGeometries, ArrangedNodes);
}
void FSoundCueGraphConnectionDrawingPolicy::BuildAudioFlowRoadmap()
{
FAudioDevice* AudioDevice = GEngine->GetAudioDevice();
if (AudioDevice)
{
USoundCueGraph* SoundCueGraph = CastChecked<USoundCueGraph>(GraphObj);
USoundCue* SoundCue = SoundCueGraph->GetSoundCue();
UAudioComponent* PreviewAudioComponent = GEditor->GetPreviewAudioComponent();
if (PreviewAudioComponent && PreviewAudioComponent->IsPlaying() && PreviewAudioComponent->Sound == SoundCue)
{
TArray<FWaveInstance*> WaveInstances;
const int32 FirstActiveIndex = AudioDevice->GetSortedActiveWaveInstances(WaveInstances, ESortedActiveWaveGetType::QueryOnly);
// Run through the active instances and cull out anything that isn't related to this graph
if (FirstActiveIndex > 0)
{
WaveInstances.RemoveAt(0, FirstActiveIndex + 1);
}
for (int32 WaveIndex = WaveInstances.Num() - 1; WaveIndex >= 0 ; --WaveIndex)
{
UAudioComponent* WaveInstanceAudioComponent = WaveInstances[WaveIndex]->ActiveSound->AudioComponent.Get();
if (WaveInstanceAudioComponent != PreviewAudioComponent)
{
WaveInstances.RemoveAtSwap(WaveIndex);
}
}
for (int32 WaveIndex = 0; WaveIndex < WaveInstances.Num(); ++WaveIndex)
{
TArray<USoundNode*> PathToWaveInstance;
if (SoundCue->FindPathToNode(WaveInstances[WaveIndex]->WaveInstanceHash, PathToWaveInstance))
{
TArray<USoundCueGraphNode_Root*> RootNode;
TArray<UEdGraphNode*> GraphNodes;
SoundCueGraph->GetNodesOfClass<USoundCueGraphNode_Root>(RootNode);
check(RootNode.Num() == 1);
GraphNodes.Add(RootNode[0]);
TArray<double> NodeTimes;
NodeTimes.Add(FApp::GetCurrentTime()); // Time for the root node
for (int32 i = 0; i < PathToWaveInstance.Num(); ++i)
{
const double ObservationTime = FApp::GetCurrentTime() + 1.f;
NodeTimes.Add(ObservationTime);
GraphNodes.Add(PathToWaveInstance[i]->GraphNode);
}
// Record the unique node->node pairings, keeping only the most recent times for each pairing
for (int32 i = GraphNodes.Num() - 1; i >= 1; --i)
{
UEdGraphNode* CurNode = GraphNodes[i];
double CurNodeTime = NodeTimes[i];
UEdGraphNode* NextNode = GraphNodes[i-1];
double NextNodeTime = NodeTimes[i-1];
FExecPairingMap& Predecessors = PredecessorNodes.FindOrAdd(NextNode);
// Update the timings if this is a more recent pairing
FTimePair& Timings = Predecessors.FindOrAdd(CurNode);
if (Timings.ThisExecTime < NextNodeTime)
{
Timings.PredExecTime = CurNodeTime;
Timings.ThisExecTime = NextNodeTime;
}
}
}
}
}
}
}
// Give specific editor modes a chance to highlight this connection or darken non-interesting connections
void FSoundCueGraphConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ float& Thickness, /*inout*/ FLinearColor& WireColor, /*inout*/bool& bDrawBubbles, /*inout*/bool& bBidirectional)
{
// Get the schema and grab the default color from it
check(OutputPin);
check(GraphObj);
const UEdGraphSchema* Schema = GraphObj->GetSchema();
WireColor = Schema->GetPinTypeColor(OutputPin->PinType);
if (InputPin == NULL)
{
return;
}
bool bExecuted = false;
// Run thru the predecessors, and on
if (FExecPairingMap* PredecessorMap = PredecessorNodes.Find(InputPin->GetOwningNode()))
{
if (FTimePair* Times = PredecessorMap->Find(OutputPin->GetOwningNode()))
{
bExecuted = true;
Thickness = ActiveWireThickness;
WireColor = ActiveColor;
bDrawBubbles = true;
}
}
if (!bExecuted)
{
// It's not followed, fade it and keep it thin
WireColor = InactiveColor;
Thickness = InactiveWireThickness;
}
}
/////////////////////////////////////////////////////
// FMaterialGraphConnectionDrawingPolicy
FMaterialGraphConnectionDrawingPolicy::FMaterialGraphConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements, UEdGraph* InGraphObj)
: FConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, ZoomFactor, InClippingRect, InDrawElements)
, MaterialGraph(CastChecked<UMaterialGraph>(InGraphObj))
, MaterialGraphSchema(CastChecked<UMaterialGraphSchema>(InGraphObj->GetSchema()))
{
// Don't want to draw ending arrowheads
ArrowImage = nullptr;
ArrowRadius = FVector2D::ZeroVector;
// Still need to be able to perceive the graph while dragging connectors, esp over comment boxes
HoverDeemphasisDarkFraction = 0.4f;
}
void FMaterialGraphConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ float& Thickness, /*inout*/ FLinearColor& WireColor, /*inout*/bool& bDrawBubbles, /*inout*/ bool& bBidirectional)
{
WireColor = MaterialGraphSchema->ActivePinColor;
// Have to consider both pins as the input will be an 'output' when previewing a connection
if (OutputPin)
{
if (!MaterialGraph->IsInputActive(OutputPin))
{
WireColor = MaterialGraphSchema->InactivePinColor;
}
}
if (InputPin)
{
if (!MaterialGraph->IsInputActive(InputPin))
{
WireColor = MaterialGraphSchema->InactivePinColor;
}
}
const bool bDeemphasizeUnhoveredPins = HoveredPins.Num() > 0;
if (bDeemphasizeUnhoveredPins)
{
ApplyHoverDeemphasis(OutputPin, InputPin, /*inout*/ Thickness, /*inout*/ WireColor);
}
}