1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //------------------------------------------------------------
5 namespace System.Activities.Presentation.FreeFormEditing
8 using System.Collections.Generic;
9 using System.Diagnostics;
10 using System.Diagnostics.CodeAnalysis;
11 using System.Globalization;
13 using System.Windows.Controls;
15 using System.Activities.Presentation.View;
16 using System.Activities.Presentation.Internal.PropertyEditing;
17 using System.Windows.Input;
20 // ConnectorRouter class implements the cannonical Hightower's algorithm.
21 // See reference paper: Hightower, "A solution to line-routing problem on the continuous plane", DAC-69.
23 // Algorithm advantages:
25 // 2. Low memory requirement.
28 // Algorithm disadvantages:
29 // 1. Do not guarantee to generate the shortest path.
30 // 2. May fail to find a route even if it does exist.
33 internal static class ConnectorRouter
35 const int connectorMargin = 30;
36 // Used for second refinement algorithm: prevent segment overlapping with the shape edge.
37 const int lineMargin = 10;
38 // Used for inner blockage of the source/destination shape.
39 const double delta = 1;
40 // The tolerance between the end point of the routed line and the source/destination ConnectionPoint.
41 internal const double EndPointTolerance = 1;
54 //This is used for dragging source point of a connector.(In this case we know only the destination connection point).
55 internal static Point[] Route(FreeFormPanel panel, Point srPoint, ConnectionPoint destConnPoint)
57 Fx.Assert(panel.IsOutmostPanel(), "panel should be the outmost FreeFormPanel");
58 UIElement destElement = destConnPoint.ParentDesigner;
59 return Route(panel, srPoint, FreeFormPanel.GetLocationRelativeToOutmostPanel(destConnPoint), null, FreeFormPanel.GetEdgeRelativeToOutmostPanel(destConnPoint), null, destElement);
62 //This is used for link creation gesture to show the adorner.(In this case we know only the source connection point).
63 internal static Point[] Route(FreeFormPanel panel, ConnectionPoint srcConnPoint, Point destPoint)
65 Fx.Assert(panel.IsOutmostPanel(), "panel should be the outmost FreeFormPanel");
66 UIElement srcElement = srcConnPoint.ParentDesigner;
67 return Route(panel, FreeFormPanel.GetLocationRelativeToOutmostPanel(srcConnPoint), destPoint, FreeFormPanel.GetEdgeRelativeToOutmostPanel(srcConnPoint), null, srcElement, null);
70 //This is used for link creation gesture to show the adorner.
71 internal static Point[] Route(FreeFormPanel panel, ConnectionPoint srcConnectionPoint, MouseEventArgs e)
73 Fx.Assert(panel.IsOutmostPanel(), "panel should be the outmost FreeFormPanel");
74 Point[] points = null;
75 // Used to keep consistency between preview and actual result.
76 // Check if we are hiting a connection point
77 if (typeof(ConnectionPointsAdorner).IsAssignableFrom(Mouse.DirectlyOver.GetType()))
79 ConnectionPointsAdorner destConnPtsAdorner = Mouse.DirectlyOver as ConnectionPointsAdorner;
80 ConnectionPoint destConnPoint = FreeFormPanel.ConnectionPointHitTest(e.GetPosition(panel), destConnPtsAdorner);
81 if (destConnPoint != null && !destConnPoint.Equals(srcConnectionPoint))
83 return Route(panel, FreeFormPanel.GetLocationRelativeToOutmostPanel(srcConnectionPoint), FreeFormPanel.GetLocationRelativeToOutmostPanel(destConnPoint),
84 FreeFormPanel.GetEdgeRelativeToOutmostPanel(srcConnectionPoint), FreeFormPanel.GetEdgeRelativeToOutmostPanel(destConnPoint), srcConnectionPoint.ParentDesigner, destConnPtsAdorner.AdornedElement);
93 points = ConnectorRouter.Route(panel, srcConnectionPoint, e.GetPosition(panel));
98 //This is used when we know both the source and destination connection points.
99 internal static Point[] Route(FreeFormPanel panel, ConnectionPoint srcConnPoint, ConnectionPoint destConnPoint)
101 Fx.Assert(panel.IsOutmostPanel(), "panel should be the outmost FreeFormPanel");
102 UIElement srcElement = srcConnPoint.ParentDesigner;
103 UIElement destElement = destConnPoint.ParentDesigner;
104 return Route(panel, FreeFormPanel.GetLocationRelativeToOutmostPanel(srcConnPoint), FreeFormPanel.GetLocationRelativeToOutmostPanel(destConnPoint),
105 FreeFormPanel.GetEdgeRelativeToOutmostPanel(srcConnPoint), FreeFormPanel.GetEdgeRelativeToOutmostPanel(destConnPoint), srcElement, destElement);
108 internal static Point GetDirection(Point from, Point to)
110 Vector vec = to - from;
112 vec.X.IsEqualTo(0) ? 0 : Math.Sign(vec.X),
113 vec.Y.IsEqualTo(0) ? 0 : Math.Sign(vec.Y)
117 static ConnectorSegment SrcEdge;
118 static ConnectorSegment DestEdge;
120 static void AddExcludedAndSrcDestRects(FreeFormPanel outmostPanel, FreeFormPanel panel, Point srcPoint, Point destPoint, UIElement srcElement, UIElement destElement, List<Rect> excludedRects, List<Rect> srcDestRects)
122 foreach (UIElement child in panel.Children)
124 if (!(child is Connector))
126 Thickness margin = new Thickness(0);
127 FrameworkElement frameworkChild = child as FrameworkElement;
128 if (frameworkChild != null)
130 margin = frameworkChild.Margin;
132 Size childSize = new Size(frameworkChild.DesiredSize.Width - margin.Left - margin.Right, frameworkChild.DesiredSize.Height - margin.Top - margin.Bottom);
133 Rect rect = new Rect(Point.Add(panel.TranslatePoint(FreeFormPanel.GetLocation(child), outmostPanel), new Vector(margin.Left, margin.Top)), childSize);
134 // We don't want to add containing rectangles to the exclusion list, otherwise the algorithm will fail to find a path
135 Rect shrunk = new Rect(rect.X + delta, rect.Y + delta, rect.Width - delta * 2, rect.Height - delta * 2);
136 if (!shrunk.Contains(srcPoint) && !shrunk.Contains(destPoint))
138 excludedRects.Add(rect);
141 if (srcElement == child)
143 srcDestRects.Add(rect);
145 else if (destElement == child)
147 srcDestRects.Add(rect);
150 UIElement element = VirtualizedContainerService.TryGetVirtualizedElement(child);
151 if (element != null && typeof(INestedFreeFormPanelContainer).IsAssignableFrom(element.GetType()))
153 FreeFormPanel childPanel = ((INestedFreeFormPanelContainer)element).GetChildFreeFormPanel();
154 if (childPanel != null)
156 AddExcludedAndSrcDestRects(outmostPanel, childPanel, srcPoint, destPoint, srcElement, destElement, excludedRects, srcDestRects);
163 internal static Point[] Route(FreeFormPanel panel, Point srcPoint, Point destPoint, List<Point> srcEdge, List<Point> destEdge, UIElement srcElement, UIElement destElement)
167 throw FxTrace.Exception.AsError(new ArgumentNullException("panel"));
169 List<Rect> excludedRects = new List<Rect>();
170 List<Rect> srcDestRects = new List<Rect>();
171 List<Point> excludedLines = new List<Point>();
172 foreach (UIElement child in panel.Children)
174 if (child.GetType() == typeof(Connector))
176 Connector connector = (Connector)child;
177 for (int i = 0; i < connector.Points.Count - 1; i++)
179 excludedLines.Add(new Point(connector.Points[i].X, connector.Points[i].Y));
180 excludedLines.Add(new Point(connector.Points[i + 1].X, connector.Points[i + 1].Y));
185 AddExcludedAndSrcDestRects(panel, panel, srcPoint, destPoint, srcElement, destElement, excludedRects, srcDestRects);
187 return Route(srcPoint, destPoint, srcEdge, destEdge, excludedRects, excludedLines, srcDestRects);
190 internal static Point[] Route(Point srcPoint, Point destPoint, List<Point> srcEdge, List<Point> destEdge, List<Rect> excludedRects, List<Point> excludedLines, List<Rect> srcDestRects)
192 ConnectorRouter.SrcEdge = null;
193 ConnectorRouter.DestEdge = null;
196 //ConnectorSegment should only be a segment from left to right or top to bottom.
197 int smallerIndex = (srcEdge[0].X < srcEdge[1].X || srcEdge[0].Y < srcEdge[1].Y) ? 0 : 1;
198 ConnectorRouter.SrcEdge = new ConnectorSegment(srcEdge[smallerIndex], srcEdge[1 - smallerIndex]);
200 if (destEdge != null)
202 int smallerIndex = (destEdge[0].X < destEdge[1].X || destEdge[0].Y < destEdge[1].Y) ? 0 : 1;
203 ConnectorRouter.DestEdge = new ConnectorSegment(destEdge[smallerIndex], destEdge[1 - smallerIndex]);
206 Rect[] srcDestRectsCopy = srcDestRects.ToArray();
207 foreach (Rect rect in srcDestRectsCopy)
209 // Add shrunk rectangle to avoid connector passing through the inner area of the src/dest rectangle
210 Rect shrunk = new Rect(rect.X + delta, rect.Y + delta, rect.Width - delta * 2, rect.Height - delta * 2);
211 srcDestRects.Add(shrunk);
212 excludedRects.Add(shrunk);
215 return TryRoute(srcPoint, destPoint, excludedRects, excludedLines, srcDestRects);
218 static Point[] TryRoute(Point srcPoint, Point destPoint, List<Rect> excludedRects, List<Point> excludedLines, List<Rect> srcDestRects)
220 Point[] segments = GetRoutedLineSegments(srcPoint, destPoint, new Size(connectorMargin, connectorMargin), excludedRects.ToArray(), excludedLines.ToArray());
222 // If we failed to find a routed path, ignore all the lines and try again.
223 if (!AreSegmentsValid(segments))
225 segments = GetRoutedLineSegments(srcPoint, destPoint, new Size(connectorMargin, connectorMargin), excludedRects.ToArray(), new Point[] { });
228 // If we failed to find a routed path, ignore all other shapes except the source and destination shape.
229 if (!AreSegmentsValid(segments))
231 segments = GetRoutedLineSegments(srcPoint, destPoint, new Size(connectorMargin, connectorMargin), srcDestRects.ToArray(), new Point[] { });
234 // If we still don't find a routed path, return the direct path.
235 if (!AreSegmentsValid(segments))
237 double slope = DesignerGeometryHelper.SlopeOfLineSegment(srcPoint, destPoint);
238 Point intermediatePoint = (slope < 1) ? new Point(destPoint.X, srcPoint.Y) : new Point(srcPoint.X, destPoint.Y);
239 segments = new Point[] { srcPoint, intermediatePoint, destPoint };
241 segments = RemoveRedundantPoints(new List<Point>(segments));
245 //In a list of points specifying a connector, remove consecutive equivalent points.
246 internal static Point[] RemoveRedundantPoints(List<Point> points)
248 for (int i = points.Count - 1; i > 0; i--)
250 if (points[i].IsEqualTo(points[i - 1]))
256 List<int> toRemove = new List<int>();
260 while (index3 < points.Count)
262 if (points[index1].X.IsEqualTo(points[index3].X) ||
263 points[index1].Y.IsEqualTo(points[index3].Y))
265 toRemove.Add(index2);
275 for (int i = points.Count - 1; i > 0; i--)
277 if (toRemove.Contains(i))
283 return points.ToArray();
286 static void AddBoundPoint(ref List<DistanceFromPoint> extremitiesList, Point p, ConnectorSegment segment, Point Z)
288 if (p.X != int.MinValue && p.X != int.MaxValue && p.Y != int.MinValue && p.Y != int.MaxValue)
290 extremitiesList.Add(new DistanceFromPoint(segment, Z, p));
294 internal static bool AreSegmentsValid(Point[] segments)
296 if (segments == null || segments.Length < 2)
301 for (int i = 1; i < segments.Length; i++)
303 if (!segments[i - 1].X.IsEqualTo(segments[i].X) && !segments[i - 1].Y.IsEqualTo(segments[i].Y))
312 [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "This is a legacy algorithm.")]
313 static Nullable<Point> EscapeAlgorithm(CoverSet coverSet, Point Z,
314 ref List<Point> escapePointsA, ref List<ConnectorSegment> horizontalSegmentsA, ref List<ConnectorSegment> verticalSegmentsA, ref List<ConnectorSegment> horizontalSegmentsB, ref List<ConnectorSegment> verticalSegmentsB,
315 ref Orientation orientationA, out ConnectorSegment intersectionSegmentA, out ConnectorSegment intersectionSegmentB, Size margin, ref bool noEscapeA)
317 Nullable<Point> intersection = null;
318 intersectionSegmentA = null;
319 intersectionSegmentB = null;
321 ConnectorSegment leftCover = coverSet.GetCover(Z, DesignerEdges.Left);
322 ConnectorSegment rightCover = coverSet.GetCover(Z, DesignerEdges.Right);
323 ConnectorSegment bottomCover = coverSet.GetCover(Z, DesignerEdges.Bottom);
324 ConnectorSegment topCover = coverSet.GetCover(Z, DesignerEdges.Top);
326 ConnectorSegment h = ConnectorSegment.SegmentFromLeftToRightCover(coverSet, Z);
327 // We do not want the routed line to coincide with the source or dest edge.
328 // Hence the edge should never be an escape line.
329 if (h.Overlaps(ConnectorRouter.SrcEdge) || h.Overlaps(ConnectorRouter.DestEdge))
335 horizontalSegmentsA.Add(h);
338 ConnectorSegment v = ConnectorSegment.SegmentFromBottomToTopCover(coverSet, Z);
339 if (v.Overlaps(ConnectorRouter.SrcEdge) || v.Overlaps(ConnectorRouter.DestEdge))
345 verticalSegmentsA.Add(v);
349 // Check if the new escape line(s) intersect with the existing ones
352 for (int i = 0; i < verticalSegmentsB.Count; i++)
354 ConnectorSegment segment = verticalSegmentsB[i];
355 intersection = h.Intersect(segment);
356 if (intersection != null)
358 intersectionSegmentA = h;
359 intersectionSegmentB = segment;
367 for (int i = 0; i < horizontalSegmentsB.Count; i++)
369 ConnectorSegment segment = horizontalSegmentsB[i];
370 intersection = v.Intersect(segment);
371 if (intersection != null)
373 intersectionSegmentA = v;
374 intersectionSegmentB = segment;
380 Nullable<Point> escapePoint = null;
383 escapePoint = EscapeProcessI(coverSet, Z, v, Orientation.Horizontal, margin);
384 if (escapePoint != null)
386 orientationA = Orientation.Vertical;
387 escapePointsA.Add((Point)escapePoint);
394 escapePoint = EscapeProcessI(coverSet, Z, h, Orientation.Vertical, margin);
395 if (escapePoint != null)
397 orientationA = Orientation.Horizontal;
398 escapePointsA.Add((Point)escapePoint);
403 bool intersectionFlag = false;
405 // Flags indicating if we can still continue in the given directions
406 bool continue1, continue2, continue3, continue4;
408 Point r1 = new Point(), r2 = new Point(), r3 = new Point(), r4 = new Point();
410 if (topCover != null)
412 r1 = new Point(Z.X, topCover.A.Y);
414 if (rightCover != null)
416 r2 = new Point(rightCover.A.X, Z.Y);
418 if (bottomCover != null)
420 r3 = new Point(Z.X, bottomCover.A.Y);
422 if (leftCover != null)
424 r4 = new Point(leftCover.A.X, Z.Y);
428 continue1 = continue2 = continue3 = continue4 = false;
429 if (topCover != null && v != null)
431 r1.Y -= margin.Height;
435 Nullable<Point> escape = EscapeProcessII(coverSet, Orientation.Vertical,
436 ref escapePointsA, ref horizontalSegmentsA, ref verticalSegmentsA, ref horizontalSegmentsB, ref verticalSegmentsB, r1, margin, out intersectionFlag, out intersectionSegmentA, out intersectionSegmentB);
439 verticalSegmentsA.Add(v);
440 if (intersectionFlag)
445 orientationA = Orientation.Horizontal;
446 coverSet.AddUsedEscapeLine(new ConnectorSegment(Z, r1));
447 coverSet.AddUsedEscapeLine(new ConnectorSegment(r1, (Point)escape));
448 escapePointsA.Add((Point)escape);
454 if (rightCover != null && h != null)
456 r2.X -= margin.Width;
460 Nullable<Point> escape = EscapeProcessII(coverSet, Orientation.Horizontal,
461 ref escapePointsA, ref horizontalSegmentsA, ref verticalSegmentsA, ref horizontalSegmentsB, ref verticalSegmentsB, r2, margin, out intersectionFlag, out intersectionSegmentA, out intersectionSegmentB);
464 horizontalSegmentsA.Add(h);
465 if (intersectionFlag)
470 orientationA = Orientation.Vertical;
471 coverSet.AddUsedEscapeLine(new ConnectorSegment(Z, r2));
472 coverSet.AddUsedEscapeLine(new ConnectorSegment(r2, (Point)escape));
473 escapePointsA.Add((Point)escape);
479 if (bottomCover != null && v != null)
481 r3.Y += margin.Height;
485 Nullable<Point> escape = EscapeProcessII(coverSet, Orientation.Vertical,
486 ref escapePointsA, ref horizontalSegmentsA, ref verticalSegmentsA, ref horizontalSegmentsB, ref verticalSegmentsB, r3, margin, out intersectionFlag, out intersectionSegmentA, out intersectionSegmentB);
489 verticalSegmentsA.Add(v);
490 if (intersectionFlag)
495 orientationA = Orientation.Horizontal;
496 coverSet.AddUsedEscapeLine(new ConnectorSegment(Z, r3));
497 coverSet.AddUsedEscapeLine(new ConnectorSegment(r3, (Point)escape));
498 escapePointsA.Add((Point)escape);
504 if (leftCover != null && h != null)
506 r4.X += margin.Width;
510 Nullable<Point> escape = EscapeProcessII(coverSet, Orientation.Horizontal,
511 ref escapePointsA, ref horizontalSegmentsA, ref verticalSegmentsA, ref horizontalSegmentsB, ref verticalSegmentsB, r4, margin, out intersectionFlag, out intersectionSegmentA, out intersectionSegmentB);
514 horizontalSegmentsA.Add(h);
515 if (intersectionFlag)
520 orientationA = Orientation.Vertical;
521 coverSet.AddUsedEscapeLine(new ConnectorSegment(Z, r4));
522 coverSet.AddUsedEscapeLine(new ConnectorSegment(r4, (Point)escape));
523 escapePointsA.Add((Point)escape);
528 } while (continue1 || continue2 || continue3 || continue4);
534 static Nullable<Point> EscapeProcessI(CoverSet coverSet, Point Z,
535 ConnectorSegment escapeLine, Orientation orientation, Size margin)
537 List<DistanceFromPoint> extremitiesList = new List<DistanceFromPoint>(4);
539 ConnectorSegment lesserCover = coverSet.GetCover(Z, (orientation == Orientation.Horizontal) ? DesignerEdges.Left : DesignerEdges.Bottom);
540 if (lesserCover != null)
542 AddBoundPoint(ref extremitiesList, lesserCover.A, lesserCover, Z);
543 AddBoundPoint(ref extremitiesList, lesserCover.B, lesserCover, Z);
546 ConnectorSegment higherCover = coverSet.GetCover(Z, (orientation == Orientation.Horizontal) ? DesignerEdges.Right : DesignerEdges.Top);
547 if (higherCover != null)
549 AddBoundPoint(ref extremitiesList, higherCover.A, higherCover, Z);
550 AddBoundPoint(ref extremitiesList, higherCover.B, higherCover, Z);
553 if (extremitiesList.Count == 0)
558 DistanceSorter.Sort(ref extremitiesList);
559 for (int i = 0; i < extremitiesList.Count; i++)
561 Point p = extremitiesList[i].P;
562 Point direction = GetDirection(Z, p);
563 if (((orientation == Orientation.Vertical) ? direction.X : direction.Y).IsEqualTo(0))
565 ConnectorSegment segment = extremitiesList[i].ConnectorSegment;
566 p = segment.ExtendPointOutwards(p);
567 direction = GetDirection(Z, p);
568 p = extremitiesList[i].P;
572 if (orientation == Orientation.Vertical)
574 side = (direction.Y < 0) ? DesignerEdges.Bottom : DesignerEdges.Top;
578 side = (direction.X < 0) ? DesignerEdges.Left : DesignerEdges.Right;
582 if ((orientation == Orientation.Vertical))
584 escapePoint = new Point(p.X + direction.X * margin.Width, Z.Y);
588 escapePoint = new Point(Z.X, p.Y + direction.Y * margin.Height);
591 ConnectorSegment newEscapeLine = new ConnectorSegment(Z, escapePoint);
592 if (!coverSet.EscapeLineHasBeenUsed(escapePoint) &&
593 escapeLine.IsPointOnSegment(escapePoint) && !escapeLine.A.IsEqualTo(escapePoint) && !escapeLine.B.IsEqualTo(escapePoint) &&
594 coverSet.IsEscapePoint(Z, escapePoint, side))
596 coverSet.AddUsedEscapeLine(newEscapeLine);
604 static Nullable<Point> EscapeProcessII(CoverSet coverSet, Orientation orientation, ref List<Point> escapePointsA,
605 ref List<ConnectorSegment> horizontalSegmentsA, ref List<ConnectorSegment> verticalSegmentsA, ref List<ConnectorSegment> horizontalSegmentsB, ref List<ConnectorSegment> verticalSegmentsB,
606 Point R, Size margin, out bool intersectionFlag, out ConnectorSegment intersectionSegmentA, out ConnectorSegment intersectionSegmentB)
608 intersectionFlag = false;
609 intersectionSegmentA = null;
610 intersectionSegmentB = null;
612 ConnectorSegment h = ConnectorSegment.SegmentFromLeftToRightCover(coverSet, R);
613 ConnectorSegment v = ConnectorSegment.SegmentFromBottomToTopCover(coverSet, R);
615 for (int i = 0; i < verticalSegmentsB.Count; i++)
617 ConnectorSegment segment = verticalSegmentsB[i];
618 Nullable<Point> intersection = h.Intersect(segment);
619 if (intersection != null)
621 intersectionFlag = true;
622 intersectionSegmentA = h;
623 intersectionSegmentB = segment;
624 escapePointsA.Add(R);
628 for (int i = 0; i < horizontalSegmentsB.Count; i++)
630 ConnectorSegment segment = horizontalSegmentsB[i];
631 Nullable<Point> intersection = v.Intersect(segment);
632 if (intersection != null)
634 intersectionFlag = true;
635 intersectionSegmentA = v;
636 intersectionSegmentB = segment;
637 escapePointsA.Add(R);
642 Nullable<Point> escapePointI = null;
644 if (orientation == Orientation.Horizontal)
646 escapePointI = EscapeProcessI(coverSet, R, v, Orientation.Horizontal, margin);
647 if (escapePointI != null)
649 verticalSegmentsA.Add(v);
650 escapePointsA.Add(R);
654 escapePointI = EscapeProcessI(coverSet, R, h, Orientation.Vertical, margin);
655 if (escapePointI != null)
657 horizontalSegmentsA.Add(h);
658 escapePointsA.Add(R);
664 escapePointI = EscapeProcessI(coverSet, R, h, Orientation.Vertical, margin);
665 if (escapePointI != null)
667 horizontalSegmentsA.Add(h);
668 escapePointsA.Add(R);
672 escapePointI = EscapeProcessI(coverSet, R, v, Orientation.Horizontal, margin);
673 if (escapePointI != null)
675 verticalSegmentsA.Add(v);
676 escapePointsA.Add(R);
684 static List<Point> FirstRefinementAlgorithm(List<Point> points, ConnectorSegment intersectionSegment)
686 List<Point> refinedSet = new List<Point>();
687 ConnectorSegment k = intersectionSegment;
689 while (points.Count > 0)
692 int i = points.Count - 1;
694 while (!k.PointLiesOnThisLine(points[i]) && i > 0)
699 while (i > 0 && k.PointLiesOnThisLine(points[i - 1]))
704 refinedSet.Add(point);
706 while (points.Count > i)
711 k = k.PerpendicularThroughPoint(point);
717 [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
718 Justification = "Catch all exceptions to prevent crash.")]
719 [SuppressMessage("Reliability", "Reliability108:IsFatalRule",
720 Justification = "Catch all exceptions to prevent crash.")]
721 static Point[] GetRoutedLineSegments(Point begin, Point end, Size margin, Rect[] rectanglesToExclude, Point[] linesToExclude)
723 if (rectanglesToExclude == null)
725 throw FxTrace.Exception.AsError(new ArgumentNullException("rectanglesToExclude"));
728 if (linesToExclude == null)
730 throw FxTrace.Exception.AsError(new ArgumentNullException("linesToExclude"));
733 if ((linesToExclude.Length % 2) > 0)
735 throw FxTrace.Exception.AsError(new ArgumentException("Error"));
739 CoverSet coverSet = new CoverSet(rectanglesToExclude, linesToExclude);
740 coverSet.ClearUsedLines();
746 List<Point> escapePointsA = new List<Point>(); //escape points from begin to end
747 List<Point> escapePointsB = new List<Point>(); //escape points from end to begin
749 //horizontal/vertical escape segments from A
750 List<ConnectorSegment> horizontalEscapeSegmentsA = new List<ConnectorSegment>();
751 List<ConnectorSegment> verticalEscapeSegmentsA = new List<ConnectorSegment>();
753 //horizontal/vertical escape segments from B
754 List<ConnectorSegment> horizontalEscapeSegmentsB = new List<ConnectorSegment>();
755 List<ConnectorSegment> verticalEscapeSegmentsB = new List<ConnectorSegment>();
757 Orientation orientationA = Orientation.Horizontal;
758 Orientation orientationB = Orientation.Horizontal;
760 escapePointsA.Add(begin);
761 escapePointsB.Add(end);
763 bool noEscapeA = false;
764 bool noEscapeB = false;
766 Nullable<Point> intersection = null;
767 ConnectorSegment intersectionSegmentA = null;
768 ConnectorSegment intersectionSegmentB = null;
782 List<Point> tempList = escapePointsA;
783 escapePointsA = escapePointsB;
784 escapePointsB = tempList;
790 bool tempBool = noEscapeA;
791 noEscapeA = noEscapeB;
792 noEscapeB = tempBool;
794 Orientation tempOrientation = orientationA;
795 orientationA = orientationB;
796 orientationB = tempOrientation;
798 List<ConnectorSegment> tempListSegm = horizontalEscapeSegmentsA;
799 horizontalEscapeSegmentsA = horizontalEscapeSegmentsB;
800 horizontalEscapeSegmentsB = tempListSegm;
802 tempListSegm = verticalEscapeSegmentsA;
803 verticalEscapeSegmentsA = verticalEscapeSegmentsB;
804 verticalEscapeSegmentsB = tempListSegm;
810 Point objectPoint = escapePointsA[escapePointsA.Count - 1];
812 intersection = EscapeAlgorithm(coverSet, objectPoint,
813 ref escapePointsA, ref horizontalEscapeSegmentsA, ref verticalEscapeSegmentsA, ref horizontalEscapeSegmentsB, ref verticalEscapeSegmentsB, ref orientationA,
814 out intersectionSegmentA, out intersectionSegmentB, margin, ref noEscapeA);
815 if (intersection != null)
821 List<Point> tempList = escapePointsA;
822 escapePointsA = escapePointsB;
823 escapePointsB = tempList;
829 bool tempBool = noEscapeA;
830 noEscapeA = noEscapeB;
831 noEscapeB = tempBool;
833 Orientation tempOrientation = orientationA;
834 orientationA = orientationB;
835 orientationB = tempOrientation;
837 List<ConnectorSegment> tempListSegm = horizontalEscapeSegmentsA;
838 horizontalEscapeSegmentsA = horizontalEscapeSegmentsB;
839 horizontalEscapeSegmentsB = tempListSegm;
841 tempListSegm = verticalEscapeSegmentsA;
842 verticalEscapeSegmentsA = verticalEscapeSegmentsB;
843 verticalEscapeSegmentsB = tempListSegm;
848 if (intersection == null)
853 List<Point> refinedPath = new List<Point>();
855 escapePointsA = FirstRefinementAlgorithm(escapePointsA, intersectionSegmentA);
856 escapePointsB = FirstRefinementAlgorithm(escapePointsB, intersectionSegmentB);
858 for (int j = escapePointsA.Count - 1; j >= 0; j--)
860 refinedPath.Add(escapePointsA[j]);
862 refinedPath.Add((Point)intersection);
863 for (int j = 0; j < escapePointsB.Count; j++)
865 refinedPath.Add(escapePointsB[j]);
868 SecondRefinementAlgorithm(coverSet, ref refinedPath, margin);
870 if (refinedPath.Count > 1 && refinedPath[refinedPath.Count - 1].IsEqualTo(begin))
872 refinedPath.Reverse();
875 return refinedPath.ToArray();
883 static void EraseRedundentWarps(CoverSet coverSet, ref List<Point> refinedPath)
885 bool structureChanged;
888 structureChanged = false;
889 List<Point> newPath = new List<Point>();
890 int currentSegment = 0;
891 while (currentSegment < refinedPath.Count - 1)
893 Point a1 = refinedPath[currentSegment];
894 Point a2 = refinedPath[currentSegment + 1];
896 ConnectorSegment a = ConnectorSegment.ConstructBoundSegment(coverSet, a1, a2);
898 int intersectingSegment = currentSegment + 2;
899 while (intersectingSegment < refinedPath.Count - 1)
901 Point b1 = refinedPath[intersectingSegment];
902 Point b2 = refinedPath[intersectingSegment + 1];
903 ConnectorSegment b = ConnectorSegment.ConstructBoundSegment(coverSet, b1, b2);
905 Nullable<Point> intersection = a.Intersect(b);
906 if (intersection != null)
908 structureChanged = true;
910 for (int i = 0; i <= currentSegment; i++)
912 newPath.Add(refinedPath[i]);
914 newPath.Add((Point)intersection);
915 for (int i = intersectingSegment + 1; i < refinedPath.Count; i++)
917 newPath.Add(refinedPath[i]);
920 List<Point> temp = refinedPath;
921 refinedPath = newPath;
925 intersectingSegment = currentSegment + 2;
929 intersectingSegment++;
934 } while (structureChanged);
937 static void SecondRefinementAlgorithm(CoverSet coverSet, ref List<Point> refinedPath, Size margin)
939 EraseRedundentWarps(coverSet, ref refinedPath);
940 List<Point> newPath = new List<Point>();
941 int currentSegment = 0;
942 while (currentSegment < refinedPath.Count - 1)
944 Point a1 = refinedPath[currentSegment];
945 Point a2 = refinedPath[currentSegment + 1];
947 bool intersected = false;
948 ConnectorSegment a = ConnectorSegment.ConstructBoundSegment(coverSet, a1, a2);
951 Vector sub = a2 - a1;
952 int steps = (int)Math.Max(Math.Abs(sub.X / margin.Width), Math.Abs(sub.Y / margin.Height)); //one of the values will be null
953 Point direction = GetDirection(a1, a2);
954 // Prevent segments overlapping with shape edges.
955 ExtendCoversOutwards(coverSet, a.Orientation, lineMargin);
957 for (int i = 1; i <= steps; i++)
959 Point k = new Point(a1.X + i * margin.Width * direction.X, a1.Y + i * margin.Height * direction.Y);
965 ConnectorSegment b = ConnectorSegment.ConstructBoundSegment(coverSet, k, (a.Orientation == Orientation.Horizontal) ? Orientation.Vertical : Orientation.Horizontal);
966 int intersectingSegment = currentSegment + 2;
967 while (intersectingSegment < refinedPath.Count - 1 && !intersected)
969 Point c1 = refinedPath[intersectingSegment];
970 Point c2 = refinedPath[intersectingSegment + 1];
971 ConnectorSegment c = new ConnectorSegment(c1, c2);
973 Nullable<Point> intersection = b.Intersect(c);
974 if (intersection != null && c.IsPointOnSegment((Point)intersection))
979 for (int j = 0; j <= currentSegment; j++)
981 newPath.Add(refinedPath[j]);
984 newPath.Add((Point)intersection);
985 for (int j = intersectingSegment + 1; j < refinedPath.Count; j++)
987 newPath.Add(refinedPath[j]);
989 List<Point> temp = refinedPath;
990 refinedPath = newPath;
996 intersectingSegment++;
1004 // Restore shape edges.
1005 ShrinkCoversInwards(coverSet, a.Orientation, lineMargin);
1014 static List<ConnectorSegment> GetSegmentsForOrientation(CoverSet coverSet, Orientation orientation)
1016 List<ConnectorSegment> connectorSegments = null;
1017 if (orientation == Orientation.Horizontal)
1019 connectorSegments = coverSet.HorizontalCovers;
1023 connectorSegments = coverSet.VerticalCovers;
1025 return connectorSegments;
1028 static void ShrinkCoversInwards(CoverSet coverSet, Orientation orientation, double shrunkLength)
1030 List<ConnectorSegment> connectorSegments = GetSegmentsForOrientation(coverSet, orientation);
1031 foreach (ConnectorSegment connSeg in connectorSegments)
1033 connSeg.ShrinkSegmentInwards(shrunkLength);
1037 static void ExtendCoversOutwards(CoverSet coverSet, Orientation orientation, double extendedLength)
1039 List<ConnectorSegment> connectorSegments = GetSegmentsForOrientation(coverSet, orientation);
1040 foreach (ConnectorSegment connSeg in connectorSegments)
1042 connSeg.ExtendSegmentOutwards(extendedLength);
1047 struct DistanceFromPoint
1049 public ConnectorSegment ConnectorSegment;
1050 public double Distance;
1053 public DistanceFromPoint(ConnectorSegment segment, Point z, Point p)
1055 this.ConnectorSegment = segment;
1057 this.Distance = DesignerGeometryHelper.DistanceBetweenPoints(z, p);
1061 // Represents a segment - the main entity in the routing algorithm
1062 sealed class ConnectorSegment
1064 Orientation orientation;
1068 public ConnectorSegment(Point point1, Point point2)
1070 if (!point1.X.IsEqualTo(point2.X) && !point1.Y.IsEqualTo(point2.Y))
1072 throw FxTrace.Exception.AsError(new InvalidOperationException(string.Format(CultureInfo.InvariantCulture,
1073 SR.CannotConstructConnectionSegment, point1.ToString(), point2.ToString())));
1076 this.point1 = point1;
1077 this.point2 = point2;
1078 this.orientation = (this.point1.X.IsEqualTo(this.point2.X) ? Orientation.Vertical : Orientation.Horizontal);
1097 public Orientation Orientation
1101 return this.orientation;
1105 public static ConnectorSegment ConstructBoundSegment(CoverSet coverSet, Point a, Point b)
1107 if (!a.X.IsEqualTo(b.X) && !a.Y.IsEqualTo(b.Y))
1112 return ConstructBoundSegment(coverSet, a, a.X.IsEqualTo(b.X) ? Orientation.Vertical : Orientation.Horizontal);
1115 public static ConnectorSegment ConstructBoundSegment(CoverSet coverSet, Point a, Orientation orientation)
1117 return (orientation == Orientation.Horizontal) ? SegmentFromLeftToRightCover(coverSet, a) : SegmentFromBottomToTopCover(coverSet, a);
1120 public static ConnectorSegment SegmentFromBottomToTopCover(CoverSet coverSet, Point p)
1122 ConnectorSegment bottomCover = coverSet.GetCover(p, DesignerEdges.Bottom);
1123 ConnectorSegment topCover = coverSet.GetCover(p, DesignerEdges.Top);
1125 //construct vertical escape segment
1126 Point bottom = new Point(p.X, (bottomCover != null) ? bottomCover.A.Y : int.MinValue);
1127 Point top = new Point(p.X, (topCover != null) ? topCover.A.Y : int.MaxValue);
1128 ConnectorSegment v = new ConnectorSegment(bottom, top);
1132 public static ConnectorSegment SegmentFromLeftToRightCover(CoverSet coverSet, Point p)
1134 ConnectorSegment leftCover = coverSet.GetCover(p, DesignerEdges.Left);
1135 ConnectorSegment rightCover = coverSet.GetCover(p, DesignerEdges.Right);
1137 //construct horizontal escape segment
1138 Point left = new Point((leftCover != null) ? leftCover.A.X : int.MinValue, p.Y);
1139 Point right = new Point((rightCover != null) ? rightCover.A.X : int.MaxValue, p.Y);
1140 ConnectorSegment h = new ConnectorSegment(left, right);
1144 public bool Covers(Point p)
1146 return (this.orientation == Orientation.Horizontal) ?
1147 (p.X.IsNoLessThan(Math.Min(this.point1.X, this.point2.X)) && p.X.IsNoGreaterThan(Math.Max(this.point1.X, this.point2.X))) :
1148 (p.Y.IsNoLessThan(Math.Min(this.point1.Y, this.point2.Y)) && p.Y.IsNoGreaterThan(Math.Max(this.point1.Y, this.point2.Y)));
1151 public override bool Equals(object obj)
1153 ConnectorSegment segment = obj as ConnectorSegment;
1154 if (segment == null)
1158 return (this.point1.IsEqualTo(segment.A) && this.point2.IsEqualTo(segment.B) && Orientation == segment.Orientation);
1161 public bool Overlaps(ConnectorSegment segment)
1163 if (segment == null)
1167 if (this.Orientation == segment.Orientation)
1169 return this.IsPointOnSegment(segment.point1) || this.IsPointOnSegment(segment.point2) || segment.IsPointOnSegment(this.point1) || segment.IsPointOnSegment(this.point2);
1174 public Point ExtendPointOutwards(Point p)
1176 if (!p.IsEqualTo(this.point1) && !p.IsEqualTo(this.point2))
1181 double k = ((this.orientation == Orientation.Horizontal) ? p.X : p.Y);
1182 double k1 = ((this.orientation == Orientation.Horizontal) ? this.point1.X : this.point1.Y);
1183 double k2 = ((this.orientation == Orientation.Horizontal) ? this.point2.X : this.point2.Y);
1184 k += k.IsEqualTo(Math.Min(k1, k2)) ? -1.0 : 1.0;
1185 return new Point((this.orientation == Orientation.Horizontal) ? k : p.X, (this.orientation == Orientation.Horizontal) ? p.Y : k);
1188 public void ExtendSegmentOutwards(double extendedLength)
1190 if (this.Orientation == Orientation.Horizontal)
1192 if (this.point1.X > this.point2.X)
1194 this.point1 = Point.Add(this.point1, new Vector(extendedLength, 0));
1195 this.point2 = Point.Add(this.point2, new Vector(-extendedLength, 0));
1199 this.point1 = Point.Add(this.point1, new Vector(-extendedLength, 0));
1200 this.point2 = Point.Add(this.point2, new Vector(extendedLength, 0));
1205 if (this.point1.Y > this.point2.Y)
1207 this.point1 = Point.Add(this.point1, new Vector(0, extendedLength));
1208 this.point2 = Point.Add(this.point2, new Vector(0, -extendedLength));
1212 this.point1 = Point.Add(this.point1, new Vector(0, -extendedLength));
1213 this.point2 = Point.Add(this.point2, new Vector(0, extendedLength));
1218 public void ShrinkSegmentInwards(double shrunkLength)
1220 this.ExtendSegmentOutwards(-shrunkLength);
1223 public override int GetHashCode()
1225 return this.point1.GetHashCode() ^ this.point2.GetHashCode() ^ Orientation.GetHashCode();
1228 public Nullable<Point> Intersect(ConnectorSegment segment)
1230 if (this.orientation == segment.Orientation)
1235 ConnectorSegment vertical = (this.orientation == Orientation.Vertical) ? this : segment;
1236 ConnectorSegment horizontal = (this.orientation == Orientation.Vertical) ? segment : this;
1238 if (vertical.A.X < Math.Min(horizontal.A.X, horizontal.B.X) || vertical.A.X > Math.Max(horizontal.A.X, horizontal.B.X))
1243 if (horizontal.A.Y < Math.Min(vertical.A.Y, vertical.B.Y) || horizontal.A.Y > Math.Max(vertical.A.Y, vertical.B.Y))
1248 return new Point(vertical.A.X, horizontal.A.Y);
1251 public bool IsPointOnSegment(Point p)
1253 if ((this.orientation == Orientation.Horizontal && !p.Y.IsEqualTo(this.point1.Y)) || (this.orientation == Orientation.Vertical && !p.X.IsEqualTo(this.point1.X)))
1258 double k = (this.orientation == Orientation.Horizontal) ? p.X : p.Y;
1259 double k1 = (this.orientation == Orientation.Horizontal) ? this.point1.X : this.point1.Y;
1260 double k2 = (this.orientation == Orientation.Horizontal) ? this.point2.X : this.point2.Y;
1261 return k.IsNoLessThan(Math.Min(k1, k2)) && k.IsNoGreaterThan(Math.Max(k1, k2));
1264 public ConnectorSegment PerpendicularThroughPoint(Point p)
1266 Orientation newOrientation = (this.orientation == Orientation.Horizontal) ? Orientation.Vertical : Orientation.Horizontal;
1267 Point newPoint = new Point(p.X, p.Y);
1268 if (newOrientation == Orientation.Horizontal)
1270 newPoint.X = int.MaxValue;
1274 newPoint.Y = int.MaxValue;
1277 return new ConnectorSegment(p, newPoint);
1280 // We consider the whole line to which this segment belongs for this test
1281 public bool PointLiesOnThisLine(Point p)
1283 return (this.orientation == Orientation.Horizontal) ? p.Y.IsEqualTo(this.point1.Y) : p.X.IsEqualTo(this.point1.X);
1287 sealed class CoverSet
1289 List<ConnectorSegment> horizontalCovers = new List<ConnectorSegment>();
1290 List<ConnectorSegment> usedEscapeLine = new List<ConnectorSegment>();
1291 List<ConnectorSegment> verticalCovers = new List<ConnectorSegment>();
1293 public List<ConnectorSegment> HorizontalCovers
1295 get { return this.horizontalCovers; }
1298 public List<ConnectorSegment> VerticalCovers
1300 get { return this.verticalCovers; }
1303 public CoverSet(Rect[] rectanglesToExclude, Point[] linesToExclude)
1305 foreach (Rect rectangle in rectanglesToExclude)
1307 AddCover(new ConnectorSegment(new Point(rectangle.Left, rectangle.Top), new Point(rectangle.Left, rectangle.Bottom)));
1308 AddCover(new ConnectorSegment(new Point(rectangle.Right, rectangle.Top), new Point(rectangle.Right, rectangle.Bottom)));
1309 AddCover(new ConnectorSegment(new Point(rectangle.Left, rectangle.Top), new Point(rectangle.Right, rectangle.Top)));
1310 AddCover(new ConnectorSegment(new Point(rectangle.Left, rectangle.Bottom), new Point(rectangle.Right, rectangle.Bottom)));
1313 for (int i = 0; i < linesToExclude.Length / 2; i++)
1315 AddCover(new ConnectorSegment(linesToExclude[i * 2], linesToExclude[(i * 2) + 1]));
1319 public void AddCover(ConnectorSegment cover)
1321 List<ConnectorSegment> covers = (cover.Orientation == Orientation.Vertical) ? this.verticalCovers : this.horizontalCovers;
1323 for (int i = 0; i < covers.Count; i++)
1325 ConnectorSegment existingCover = covers[i];
1326 if (cover.IsPointOnSegment(existingCover.A) && cover.IsPointOnSegment(existingCover.B))
1331 else if (existingCover.IsPointOnSegment(cover.A) && existingCover.IsPointOnSegment(cover.B))
1341 public void AddUsedEscapeLine(ConnectorSegment segment)
1343 this.usedEscapeLine.Add(segment);
1346 public void ClearUsedLines()
1348 this.usedEscapeLine.Clear();
1351 public bool EscapeLineHasBeenUsed(Point escapePoint)
1353 for (int i = 0; i < this.usedEscapeLine.Count; i++)
1355 ConnectorSegment usedSegment = this.usedEscapeLine[i];
1356 if (usedSegment.IsPointOnSegment(escapePoint))
1364 // Gets the cover on the given side (closest cover to the given side) for the point out of all stored segments
1365 public ConnectorSegment GetCover(Point p, DesignerEdges side)
1367 ConnectorSegment cover = null;
1370 if (side == DesignerEdges.Left || side == DesignerEdges.Right)
1372 for (int i = 0; i < this.verticalCovers.Count; i++)
1374 ConnectorSegment segment = this.verticalCovers[i];
1375 int currentDistance = (int)((side == DesignerEdges.Left) ? p.X - segment.A.X : segment.A.X - p.X);
1376 if (currentDistance > 0 && segment.Covers(p))
1378 if (cover == null || distance > currentDistance)
1381 distance = currentDistance;
1388 for (int i = 0; i < this.horizontalCovers.Count; i++)
1390 ConnectorSegment segment = this.horizontalCovers[i];
1391 int currentDistance = (int)((side == DesignerEdges.Bottom) ? p.Y - segment.A.Y : segment.A.Y - p.Y);
1392 if (currentDistance > 0 && segment.Covers(p))
1394 if (cover == null || distance > currentDistance)
1397 distance = currentDistance;
1406 public List<ConnectorSegment> GetCovers(Point p, DesignerEdges side)
1408 List<ConnectorSegment> covers = new List<ConnectorSegment>();
1410 if (side == DesignerEdges.Left || side == DesignerEdges.Right)
1412 for (int i = 0; i < this.verticalCovers.Count; i++)
1414 ConnectorSegment segment = this.verticalCovers[i];
1415 int currentDistance = (int)((side == DesignerEdges.Left) ? p.X - segment.A.X : segment.A.X - p.X);
1416 if (currentDistance > 0 && segment.Covers(p))
1418 covers.Add(segment);
1424 for (int i = 0; i < this.horizontalCovers.Count; i++)
1426 ConnectorSegment segment = this.horizontalCovers[i];
1427 int currentDistance = (int)((side == DesignerEdges.Bottom) ? p.Y - segment.A.Y : segment.A.Y - p.Y);
1428 if (currentDistance > 0 && segment.Covers(p))
1430 covers.Add(segment);
1438 public bool IsEscapePoint(Point origin, Point escape, DesignerEdges side)
1440 ConnectorSegment originalCover = this.GetCover(origin, side);
1441 int originalDistance;
1442 if (side == DesignerEdges.Left || side == DesignerEdges.Right)
1444 originalDistance = (int)(originalCover.A.X - escape.X);
1448 originalDistance = (int)(originalCover.A.Y - escape.Y);
1451 if (originalCover.Covers(escape))
1456 List<ConnectorSegment> newCovers = this.GetCovers(escape, side);
1457 for (int i = 0; i < newCovers.Count; i++)
1459 ConnectorSegment newCover = newCovers[i];
1460 if (newCover == originalCover)
1466 if (side == DesignerEdges.Left || side == DesignerEdges.Right)
1468 newDistance = (int)Math.Abs(newCover.A.X - escape.X);
1472 newDistance = (int)Math.Abs(newCover.A.Y - escape.Y);
1475 if (Math.Sign(newDistance) == Math.Sign(originalDistance) && Math.Abs(newDistance) < Math.Abs(originalDistance))
1485 sealed class DistanceSorter : IComparer<DistanceFromPoint>
1491 public static void Sort(ref List<DistanceFromPoint> distances)
1493 DistanceSorter sorter = new DistanceSorter();
1494 distances.Sort(sorter);
1497 int IComparer<DistanceFromPoint>.Compare(DistanceFromPoint lhs, DistanceFromPoint rhs)
1499 if (lhs.Distance.IsEqualTo(rhs.Distance))
1503 else if (lhs.Distance > rhs.Distance)