A Demo Project for the UnrealEngineSDK
Loading...
Searching...
No Matches
DialogueGraphConnectionDrawingPolicy.cpp
Go to the documentation of this file.
1// Copyright Csaba Molnar, Daniel Butum. All Rights Reserved.
3
4#include "Rendering/DrawElements.h"
5#include "Framework/Application/SlateApplication.h"
6
10
12// FDialogueGraphConnectionDrawingPolicy
14 int32 InBackLayerID,
15 int32 InFrontLayerID,
16 float ZoomFactor,
17 const FSlateRect& InClippingRect,
18 FSlateWindowElementList& InDrawElements,
19 UEdGraph* InGraphObj
20) : Super(InBackLayerID, InFrontLayerID, ZoomFactor, InClippingRect, InDrawElements),
21 Graph(InGraphObj), DialogueSettings(GetDefault<UDlgSystemSettings>())
22{
23}
24
25void FDialogueGraphConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin,
26 /*inout*/ FConnectionParams& Params)
27{
28 // Is the connection bidirectional?
29 Params.bUserFlag1 = false;
30 Params.AssociatedPin1 = OutputPin;
31 Params.AssociatedPin2 = InputPin;
32 Params.WireThickness = DialogueSettings->WireThickness;
33 Params.bDrawBubbles = DialogueSettings->bWireDrawBubbles;
34
35 if (InputPin)
36 {
37 if (const UDialogueGraphNode_Edge* GraphNode_Edge = Cast<UDialogueGraphNode_Edge>(InputPin->GetOwningNode()))
38 {
39 Params.WireColor = GraphNode_Edge->GetEdgeColor(HoveredPins.Contains(InputPin));
40 }
41 }
42
43 const bool bDeemphasizeUnhoveredPins = HoveredPins.Num() > 0;
44 if (bDeemphasizeUnhoveredPins)
45 {
46 ApplyHoverDeemphasis(OutputPin, InputPin, /*inout*/ Params.WireThickness, /*inout*/ Params.WireColor);
47 }
48}
49
51 FArrangedChildren& ArrangedNodes,
52 TSharedRef<SWidget>& OutputPinWidget,
53 UEdGraphPin* OutputPin,
54 UEdGraphPin* InputPin,
55 /*out*/ FArrangedWidget*& StartWidgetGeometry,
56 /*out*/ FArrangedWidget*& EndWidgetGeometry
57)
58{
59 if (UDialogueGraphNode_Edge* GraphNode_Edge = Cast<UDialogueGraphNode_Edge>(InputPin->GetOwningNode()))
60 {
61 // Do not determine anything, this way the links won't be drawn
62 if (!GraphNode_Edge->ShouldDrawEdge())
63 return;
64
65 // Find the actual nodes each edge is connected to, and link to those
66 // From Parent to Child, the Edge is just a proxy
67 UDialogueGraphNode* ParentNode = GraphNode_Edge->GetParentNode();
68 UDialogueGraphNode* ChildNode = GraphNode_Edge->GetChildNode();
69 int32* PrevNodeIndex = NodeWidgetMap.Find(ParentNode);
70 int32* NextNodeIndex = NodeWidgetMap.Find(ChildNode);
71 if (PrevNodeIndex != nullptr && NextNodeIndex != nullptr)
72 {
73 StartWidgetGeometry = &(ArrangedNodes[*PrevNodeIndex]);
74 EndWidgetGeometry = &(ArrangedNodes[*NextNodeIndex]);
75 }
76 }
77 else
78 {
79 checkNoEntry();
80 //FConnectionDrawingPolicy::DetermineLinkGeometry(ArrangedNodes, OutputPinWidget, OutputPin, InputPin, StartWidgetGeometry, EndWidgetGeometry);
81 }
82}
83
85 const FGeometry& StartGeom,
86 const FGeometry& EndGeom,
87 const FConnectionParams& Params
88)
89{
90 // Draw the spline and arrow from/to the closest points between the two geometries (nodes)
91 // Get a reasonable seed point (halfway between the boxes)
92 const FVector2D StartCenter = FGeometryHelper::CenterOf(StartGeom);
93 const FVector2D EndCenter = FGeometryHelper::CenterOf(EndGeom);
94 const FVector2D SeedPoint = (StartCenter + EndCenter) / 2.0f;
95
96 // Find the (approximate) closest points between the two boxes
97 const FVector2D StartAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(StartGeom, SeedPoint);
98 const FVector2D EndAnchorPoint = FGeometryHelper::FindClosestPointOnGeom(EndGeom, SeedPoint);
99
100 DrawSplineWithArrow(StartAnchorPoint, EndAnchorPoint, Params);
101}
102
103void FDialogueGraphConnectionDrawingPolicy::DrawSplineWithArrow(const FVector2D& StartPoint, const FVector2D& EndPoint,
104 const FConnectionParams& Params)
105{
106 Internal_DrawLineWithArrow(StartPoint, EndPoint, Params);
107 // Is the connection bidirectional?
108 if (Params.bUserFlag1)
109 {
110 Internal_DrawLineWithArrow(EndPoint, StartPoint, Params);
111 }
112}
113
115 int32 LayerId,
116 const FVector2D& Start,
117 const FVector2D& End,
118 const FConnectionParams& Params
119)
120{
121 // Code mostly from Super::DrawConnection
122 const FVector2D& P0 = Start;
123 const FVector2D& P1 = End;
124
125 const FVector2D SplineTangent = ComputeSplineTangent(P0, P1);
126 const FVector2D P0Tangent = Params.StartDirection == EGPD_Output ? SplineTangent : -SplineTangent;
127 const FVector2D P1Tangent = Params.EndDirection == EGPD_Input ? SplineTangent : -SplineTangent;
128
129 if (Settings->bTreatSplinesLikePins)
130 {
131 // Distance to consider as an overlap
132 const float QueryDistanceTriggerThresholdSquared = FMath::Square(Settings->SplineHoverTolerance + Params.WireThickness * 0.5f);
133
134 // Distance to pass the bounding box cull test (may want to expand this later on if we want to do 'closest pin' actions that don't require an exact hit)
135 const float QueryDistanceToBoundingBoxSquared = QueryDistanceTriggerThresholdSquared;
136
137 bool bCloseToSpline = false;
138 {
139 // The curve will include the endpoints but can extend out of a tight bounds because of the tangents
140 // P0Tangent coefficient maximizes to 4/27 at a=1/3, and P1Tangent minimizes to -4/27 at a=2/3.
141 constexpr float MaximumTangentContribution = 4.0f / 27.0f;
142 FBox2D Bounds(ForceInit);
143
144 Bounds += FVector2D(P0);
145 Bounds += FVector2D(P0 + MaximumTangentContribution * P0Tangent);
146 Bounds += FVector2D(P1);
147 Bounds += FVector2D(P1 - MaximumTangentContribution * P1Tangent);
148
149 bCloseToSpline = Bounds.ComputeSquaredDistanceToPoint(LocalMousePosition) < QueryDistanceToBoundingBoxSquared;
150 }
151
152 if (bCloseToSpline)
153 {
154 // Find the closest approach to the spline
155 FVector2D ClosestPoint(ForceInit);
156 float ClosestDistanceSquared = FLT_MAX;
157
158 constexpr int32 NumStepsToTest = 16;
159 constexpr float StepInterval = 1.0f / static_cast<float>(NumStepsToTest);
160 FVector2D Point1 = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, 0.0f);
161 for (float TestAlpha = 0.0f; TestAlpha < 1.0f; TestAlpha += StepInterval)
162 {
163 const FVector2D Point2 = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, TestAlpha + StepInterval);
164
165 const FVector2D ClosestPointToSegment = FMath::ClosestPointOnSegment2D(LocalMousePosition, Point1, Point2);
166 const float DistanceSquared = (LocalMousePosition - ClosestPointToSegment).SizeSquared();
167
168 if (DistanceSquared < ClosestDistanceSquared)
169 {
170 ClosestDistanceSquared = DistanceSquared;
171 ClosestPoint = ClosestPointToSegment;
172 }
173
174 Point1 = Point2;
175 }
176
177 // Record the overlap
178 if (ClosestDistanceSquared < QueryDistanceTriggerThresholdSquared)
179 {
180 if (ClosestDistanceSquared < SplineOverlapResult.GetDistanceSquared())
181 {
182 const float SquaredDistToPin1 = Params.AssociatedPin1 != nullptr ? (P0 - ClosestPoint).SizeSquared() : FLT_MAX;
183
184 float SquaredDistToPin2 = FLT_MAX;
185 UEdGraphPin* InputPin = Params.AssociatedPin2;
186 // Can happen when we call DrawPreviewConnector
187 if (InputPin != nullptr)
188 {
189 // Must be Edge input pin
190 UDialogueGraphNode_Edge* GraphNode_Edge = CastChecked<UDialogueGraphNode_Edge>(Params.AssociatedPin2->GetOwningNode());
191 InputPin = GraphNode_Edge->GetChildNode()->GetInputPin();
192 SquaredDistToPin2 = (P1 - ClosestPoint).SizeSquared();
193 }
194
195 // The AssociatedPin2 is the Edge pin, which is not displayed, always hover over the current NodePin
196 SplineOverlapResult = FGraphSplineOverlapResult(Params.AssociatedPin1, Params.AssociatedPin1, ClosestDistanceSquared,
197 SquaredDistToPin1, SquaredDistToPin1);
198 }
199 }
200 }
201 }
202
203 // Draw the spline itself
204 FSlateDrawElement::MakeDrawSpaceSpline(
205 DrawElementsList,
206 LayerId,
207 P0, P0Tangent,
208 P1, P1Tangent,
209 Params.WireThickness,
210 ESlateDrawEffect::None,
211 Params.WireColor
212 );
213
214 if (Params.bDrawBubbles || (MidpointImage != nullptr))
215 {
216 // This table maps distance along curve to alpha
217 FInterpCurve<float> SplineReparamTable;
218 const float SplineLength = MakeSplineReparamTable(P0, P0Tangent, P1, P1Tangent, SplineReparamTable);
219
220 // Draw bubbles on the spline
221 if (Params.bDrawBubbles)
222 {
223 const float BubbleSpacing = 64.f * ZoomFactor;
224 const float BubbleSpeed = 192.f * ZoomFactor;
225 const FVector2D BubbleSize = BubbleImage->ImageSize * ZoomFactor * 0.1f * Params.WireThickness;
226
227 const float Time = FPlatformTime::Seconds() - GStartTime;
228 const float BubbleOffset = FMath::Fmod(Time * BubbleSpeed, BubbleSpacing);
229 const int32 NumBubbles = FMath::CeilToInt(SplineLength / BubbleSpacing);
230 for (int32 i = 0; i < NumBubbles; ++i)
231 {
232 const float Distance = (static_cast<float>(i) * BubbleSpacing) + BubbleOffset;
233 if (Distance < SplineLength)
234 {
235 const float Alpha = SplineReparamTable.Eval(Distance, 0.f);
236 FVector2D BubblePos = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, Alpha);
237 BubblePos -= (BubbleSize * 0.5f);
238
239 FSlateDrawElement::MakeBox(
240 DrawElementsList,
241 LayerId,
242 FPaintGeometry(BubblePos, BubbleSize, ZoomFactor),
243 BubbleImage,
244 ESlateDrawEffect::None,
245 Params.WireColor
246 );
247 }
248 }
249 }
250
251 // Draw the midpoint image
252 if (MidpointImage != nullptr)
253 {
254 // Determine the spline position for the midpoint
255 const float MidpointAlpha = SplineReparamTable.Eval(SplineLength * 0.5f, 0.f);
256 const FVector2D Midpoint = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, MidpointAlpha);
257
258 // Approximate the slope at the midpoint (to orient the midpoint image to the spline)
259 const FVector2D MidpointPlusE = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, MidpointAlpha + KINDA_SMALL_NUMBER);
260 const FVector2D MidpointMinusE = FMath::CubicInterp(P0, P0Tangent, P1, P1Tangent, MidpointAlpha - KINDA_SMALL_NUMBER);
261 const FVector2D SlopeUnnormalized = MidpointPlusE - MidpointMinusE;
262
263 // Draw the arrow
264 const FVector2D MidpointDrawPos = Midpoint - MidpointRadius;
265 const float AngleInRadians = SlopeUnnormalized.IsNearlyZero() ? 0.0f : FMath::Atan2(SlopeUnnormalized.Y, SlopeUnnormalized.X);
266
267 FSlateDrawElement::MakeRotatedBox(
268 DrawElementsList,
269 LayerId,
270 FPaintGeometry(MidpointDrawPos, MidpointImage->ImageSize * ZoomFactor, ZoomFactor),
271 MidpointImage,
272 ESlateDrawEffect::None,
273 AngleInRadians,
274 TOptional<FVector2D>(),
275 FSlateDrawElement::RelativeToElement,
276 Params.WireColor
277 );
278 }
279 }
280}
281
283 const FGeometry& PinGeometry,
284 const FVector2D& StartPoint,
285 const FVector2D& EndPoint,
286 UEdGraphPin* Pin
287)
288{
289 FConnectionParams Params;
290 DetermineWiringStyle(Pin, nullptr, /*inout*/ Params);
291
292 // Find closesest point to each geometry, so that we draw from that source, simulates DrawSplineWithArrow
293 if (Pin->Direction == EGPD_Output)
294 {
295 // From output pin, closest point on the SourceGeometry (source node) that goes to the EndPoint (destination node)
296 DrawSplineWithArrow(FGeometryHelper::FindClosestPointOnGeom(PinGeometry, EndPoint), EndPoint, Params);
297 }
298 else
299 {
300 // From input pin, should never happen
301 DrawSplineWithArrow(FGeometryHelper::FindClosestPointOnGeom(PinGeometry, StartPoint), StartPoint, Params);
302 }
303}
304
305FVector2D FDialogueGraphConnectionDrawingPolicy::ComputeSplineTangent(const FVector2D& Start, const FVector2D& End) const
306{
307 // Draw a straight line
308 const FVector2D Delta = End - Start;
309 const FVector2D NormDelta = Delta.GetSafeNormal();
310 return NormDelta;
311}
312
313void FDialogueGraphConnectionDrawingPolicy::Draw(TMap<TSharedRef<SWidget>, FArrangedWidget>& InPinGeometries,
314 FArrangedChildren& ArrangedNodes)
315{
316 // Build an acceleration structure to quickly find geometry for the nodes
317 NodeWidgetMap.Empty();
318 for (int32 NodeIndex = 0; NodeIndex < ArrangedNodes.Num(); ++NodeIndex)
319 {
320 FArrangedWidget& CurWidget = ArrangedNodes[NodeIndex];
321 TSharedRef<SGraphNode> ChildNode = StaticCastSharedRef<SGraphNode>(CurWidget.Widget);
322 NodeWidgetMap.Add(ChildNode->GetNodeObj(), NodeIndex);
323 }
324
325 // Now draw
326 Super::Draw(InPinGeometries, ArrangedNodes);
327}
328
330 const FVector2D& StartAnchorPoint,
331 const FVector2D& EndAnchorPoint,
332 const FConnectionParams& Params
333)
334{
335 constexpr float LineSeparationAmount = 4.5f;
336
337 const FVector2D DeltaPos = EndAnchorPoint - StartAnchorPoint;
338 const FVector2D UnitDelta = DeltaPos.GetSafeNormal();
339 const FVector2D Normal = FVector2D(DeltaPos.Y, -DeltaPos.X).GetSafeNormal();
340
341 // Come up with the final start/end points
342 const FVector2D DirectionBias = Normal * LineSeparationAmount;
343 const FVector2D LengthBias = ArrowRadius.X * UnitDelta;
344 const FVector2D StartPoint = StartAnchorPoint + DirectionBias + LengthBias;
345 const FVector2D EndPoint = EndAnchorPoint + DirectionBias - LengthBias;
346
347 // Draw a line/spline
348 DrawConnection(WireLayerID, StartPoint, EndPoint, Params);
349
350 // Draw the arrow
351 if (ArrowImage)
352 {
353 const FVector2D ArrowDrawPos = EndPoint - ArrowRadius;
354 const float AngleInRadians = FMath::Atan2(DeltaPos.Y, DeltaPos.X);
355
356 FSlateDrawElement::MakeRotatedBox(
357 DrawElementsList,
358 ArrowLayerID,
359 FPaintGeometry(ArrowDrawPos, ArrowImage->ImageSize * ZoomFactor, ZoomFactor),
360 ArrowImage,
361 ESlateDrawEffect::None,
362 AngleInRadians,
363 TOptional<FVector2D>(),
364 FSlateDrawElement::RelativeToElement,
365 Params.WireColor
366 );
367 }
368}
void DrawConnection(int32 LayerId, const FVector2D &Start, const FVector2D &End, const FConnectionParams &Params) override
void DetermineWiringStyle(UEdGraphPin *OutputPin, UEdGraphPin *InputPin, FConnectionParams &Params) override
void DrawSplineWithArrow(const FGeometry &StartGeom, const FGeometry &EndGeom, const FConnectionParams &Params) override
void DetermineLinkGeometry(FArrangedChildren &ArrangedNodes, TSharedRef< SWidget > &OutputPinWidget, UEdGraphPin *OutputPin, UEdGraphPin *InputPin, FArrangedWidget *&StartWidgetGeometry, FArrangedWidget *&EndWidgetGeometry) override
FDialogueGraphConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float ZoomFactor, const FSlateRect &InClippingRect, FSlateWindowElementList &InDrawElements, UEdGraph *InGraphObj)
FVector2D ComputeSplineTangent(const FVector2D &Start, const FVector2D &End) const override
void DrawPreviewConnector(const FGeometry &PinGeometry, const FVector2D &StartPoint, const FVector2D &EndPoint, UEdGraphPin *Pin) override
void Internal_DrawLineWithArrow(const FVector2D &StartAnchorPoint, const FVector2D &EndAnchorPoint, const FConnectionParams &Params)
void Draw(TMap< TSharedRef< SWidget >, FArrangedWidget > &PinGeometries, FArrangedChildren &ArrangedNodes) override
UEdGraphPin * GetInputPin() const
UDialogueGraphNode * GetChildNode() const
UCLASS(Config = Engine, DefaultConfig, meta = (DisplayName = "Dialogue System Settings"))
bool bWireDrawBubbles
UPROPERTY(Category = "Graph Edge", Config, EditAnywhere)
float WireThickness
UPROPERTY(Category = "Graph Edge", Config, EditAnywhere)