// 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" 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& 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(A / B, 0.0f, 1.0f); // Generate closest point return LineStart + (T * LineVector); } FVector2D FGeometryHelper::FindClosestPointOnGeom(const FGeometry& Geom, const FVector2D& TestPoint) { TArray 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((UObject*)(ActiveObjectBeingDebugged->GetClass())); FBlueprintDebugData const& ClassDebugData = Class->GetDebugData(); TSet LatentContextObjects; TArray FunctionNodes; SourceGraph->GetNodesOfClass(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(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 UUIDSet; Manager.GetActiveUUIDs(ActiveObjectBeingDebugged, /*out*/ UUIDSet); for (TSet::TConstIterator IterUUID(UUIDSet); IterUUID; ++IterUUID) { const int32 UUID = *IterUUID; if (UEdGraphNode* ParentNode = ClassDebugData.FindNodeFromUUID(UUID)) { TArray& 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(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) , ZoomFactor(InZoomFactor) , ClippingRect(InClippingRect) , DrawElementsList(InDrawElements) { ArrowImage = FEditorStyle::GetBrush( TEXT("Graph.Arrow") ); ArrowRadius = ArrowImage->ImageSize * ZoomFactor * 0.5f; MidpointImage = NULL; MidpointRadius = FVector2D::ZeroVector; 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) { const FVector2D StartPoint = FGeometryHelper::VerticalMiddleRightOf( StartGeom ); const FVector2D EndPoint = FGeometryHelper::VerticalMiddleLeftOf( EndGeom ) - FVector2D(ArrowRadius.X, 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 >& InHoveredPins, const TArray< TSharedPtr >& 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 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& 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(Start.X - End.X); const FVector2D P0Tangent = Tension * FVector2D(1.0f, 0); 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 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(), 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, FArrangedWidget>& PinGeometries, FArrangedChildren& ArrangedNodes, TSharedRef& OutputPinWidget, UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*out*/ FArrangedWidget*& StartWidgetGeometry, /*out*/ FArrangedWidget*& EndWidgetGeometry ) { StartWidgetGeometry = PinGeometries.Find(OutputPinWidget); if (TSharedRef* pTargetWidget = PinToPinWidgetMap.Find(InputPin)) { TSharedRef InputWidget = *pTargetWidget; EndWidgetGeometry = PinGeometries.Find(InputWidget); } } void FConnectionDrawingPolicy::Draw(TMap, FArrangedWidget>& PinGeometries, FArrangedChildren& ArrangedNodes) { PinToPinWidgetMap.Empty(); for (TMap, FArrangedWidget>::TIterator ConnectorIt(PinGeometries); ConnectorIt; ++ConnectorIt) { TSharedRef SomePinWidget = ConnectorIt.Key(); SGraphPin& PinWidget = static_cast(SomePinWidget.Get()); PinToPinWidgetMap.Add(PinWidget.GetPinObj(), StaticCastSharedRef(SomePinWidget)); } for (TMap, FArrangedWidget>::TIterator ConnectorIt(PinGeometries); ConnectorIt; ++ConnectorIt) { TSharedRef SomePinWidget = ConnectorIt.Key(); SGraphPin& PinWidget = static_cast(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 != NULL) && (LinkStartWidgetGeometry != NULL)) { 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); } } } } } void FConnectionDrawingPolicy::SetIncompatiblePinDrawState(const TSharedPtr& StartPin, const TSet< TSharedRef >& VisiblePins) { } void FConnectionDrawingPolicy::ResetIncompatiblePinDrawState(const TSet< TSharedRef >& 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 DarkFraction = 0.8f; 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(WireColor, LightenedColor, LightFraction * TimeFraction); } else { WireColor = FMath::Lerp(WireColor, DarkenedColor, DarkFraction * 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) { const UGraphEditorSettings* Settings = GetDefault(); // Don't want to draw ending arrowheads ArrowImage = nullptr; ArrowRadius = FVector2D::ZeroVector; // But we do want to draw midpoint arrowheads if (GetDefault()->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, 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(ActiveObject->GetClass()->ClassGeneratedBy); } TArray SequentialNodesInGraph; TArray SequentialNodeTimes; TArray SequentialExecPinsInGraph; { const TSimpleRingBuffer& TraceStack = FKismetDebugUtilities::GetTraceStack(); UBlueprintGeneratedClass* TargetClass = Cast(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(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 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(*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(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(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(UnclampedReleaseRatio, 0.0f, 1.0f); SustainAlpha = FMath::Pow(ClampedReleaseRatio, ReleaseExponent); } } bool FKismetConnectionDrawingPolicy::TreatWireAsExecutionPin(UEdGraphPin* InputPin, UEdGraphPin* OutputPin) const { const UEdGraphSchema_K2* Schema = GetDefault(); 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(SustainWireThickness, AttackWireThickness, AttackAlpha); Thickness = FMath::Lerp(ReleaseWireThickness, DecayedAttackThickness, SustainAlpha); const FLinearColor DecayedAttackColor = FMath::Lerp(SustainColor, AttackColor, AttackAlpha); WireColor = WireColor * FMath::Lerp(ReleaseColor, DecayedAttackColor, SustainAlpha); if (SustainAlpha > KINDA_SMALL_NUMBER) { bDrawBubbles = true; } } // 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(Schema); if (K2Schema != NULL) { if (TreatWireAsExecutionPin(InputPin, OutputPin)) { if (CanBuildRoadmap()) { // track if this node connection was ran or not bool bExecuted = false; UEdGraphNode* InputNode = InputPin->GetOwningNode(); // 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* ExecTiming = ExecPaths->Find(OutputPin)) { 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& StartPin, const TSet< TSharedRef >& VisiblePins) { ResetIncompatiblePinDrawState(VisiblePins); for(auto VisiblePinIterator = VisiblePins.CreateConstIterator(); VisiblePinIterator; ++VisiblePinIterator) { TSharedPtr CheckPin = StaticCastSharedRef(*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 >& VisiblePins) { for(auto VisiblePinIterator = VisiblePins.CreateConstIterator(); VisiblePinIterator; ++VisiblePinIterator) { TSharedPtr VisiblePin = StaticCastSharedRef(*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(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, FArrangedWidget>& PinGeometries, FArrangedChildren& ArrangedNodes, TSharedRef& OutputPinWidget, UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*out*/ FArrangedWidget*& StartWidgetGeometry, /*out*/ FArrangedWidget*& EndWidgetGeometry ) { if (UAnimStateEntryNode* EntryNode = Cast(OutputPin->GetOwningNode())) { //FConnectionDrawingPolicy::DetermineLinkGeometry(PinGeometries, ArrangedNodes, OutputPinWidget, OutputPin, InputPin, StartWidgetGeometry, EndWidgetGeometry); StartWidgetGeometry = PinGeometries.Find(OutputPinWidget); UAnimStateNodeBase* State = CastChecked(InputPin->GetOwningNode()); int32 StateIndex = NodeWidgetMap.FindChecked(State); EndWidgetGeometry = &(ArrangedNodes(StateIndex)); } else if (UAnimStateTransitionNode* TransNode = Cast(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* pTargetWidget = PinToPinWidgetMap.Find(InputPin)) { TSharedRef InputWidget = *pTargetWidget; EndWidgetGeometry = PinGeometries.Find(InputWidget); } } } void FStateMachineConnectionDrawingPolicy::Draw(TMap, 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 ChildNode = StaticCastSharedRef(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(), 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(); return (InputPin != NULL) && (Schema->IsPosePin(OutputPin->PinType)); } void FAnimGraphConnectionDrawingPolicy::BuildExecutionRoadmap() { UAnimBlueprint* TargetBP = CastChecked(FBlueprintEditorUtils::FindBlueprintForGraphChecked(GraphObj)); UAnimBlueprintGeneratedClass* AnimBlueprintClass = (UAnimBlueprintGeneratedClass*)(*(TargetBP->GeneratedClass)); if (TargetBP->GetObjectBeingDebugged() == NULL) { return; } TMap 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(PropertySourceMap.FindRef(AnimBlueprintClass->AnimNodeProperties[VisitRecord.SourceID]))) { if (UAnimGraphNode_Base* TargetNode = Cast(PropertySourceMap.FindRef(AnimBlueprintClass->AnimNodeProperties[VisitRecord.TargetID]))) { UEdGraphPin* PoseNet = NULL; UAnimationGraphSchema const* AnimSchema = GetDefault(); 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(LightBlendThickness, HeavyBlendThickness, BlendWeight); WireColor = WireColor * (BlendWeight * 0.5f + 0.5f);//FMath::Lerp(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 const UGraphEditorSettings* Settings = GetDefault(); 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, 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(GraphObj); USoundCue* SoundCue = SoundCueGraph->GetSoundCue(); UAudioComponent* PreviewAudioComponent = GEditor->GetPreviewAudioComponent(); if (PreviewAudioComponent && PreviewAudioComponent->IsPlaying() && PreviewAudioComponent->Sound == SoundCue) { TArray 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 PathToWaveInstance; if (SoundCue->FindPathToNode(WaveInstances[WaveIndex]->WaveInstanceHash, PathToWaveInstance)) { TArray RootNode; TArray GraphNodes; SoundCueGraph->GetNodesOfClass(RootNode); check(RootNode.Num() == 1); GraphNodes.Add(RootNode[0]); TArray 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(InGraphObj)) , MaterialGraphSchema(CastChecked(InGraphObj->GetSchema())) { // Don't want to draw ending arrowheads ArrowImage = nullptr; ArrowRadius = FVector2D::ZeroVector; } 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); } }