using System; using System.Collections.Generic; using System.Windows; using System.Windows.Documents; using AIStudio.Wpf.DiagramDesigner.Geometrys; namespace AIStudio.Wpf.DiagramDesigner { internal class PathFinder { const int margin = 8; const double CORNER = 6; private static List AddCornerPoints(List linePoints) { if (linePoints.Count < 3 || CORNER <= 0) return linePoints; List points = new List(); points.Add(linePoints[0]); for (int i = 1; i < linePoints.Count - 1; i++) { double x1 = linePoints[i].X; double x0 = linePoints[i - 1].X; double y1 = linePoints[i].Y; double y0 = linePoints[i - 1].Y; if (x1 == x0) { double x2 = linePoints[i + 1].X; if (y1 > y0) { if (y1 - y0 > CORNER && Math.Abs(x2 - x1) > CORNER) { points.Add(new PointBase(x1, y1 - CORNER)); points.Add(new PointBase(x2 > x1 ? x1 + CORNER : x1 - CORNER, y1)); } } else { if (y0 - y1 > CORNER && Math.Abs(x2 - x1) > CORNER) { points.Add(new PointBase(x1, y1 + CORNER)); points.Add(new PointBase(x2 > x1 ? x1 + CORNER : x1 - CORNER, y1)); } } } else if (y1 == y0) { double y2 = linePoints[i + 1].Y; if (x1 > x0) { if (x1 - x0 > CORNER && Math.Abs(y2 - y1) > CORNER) { points.Add(new PointBase(x1 - CORNER, y1)); points.Add(new PointBase(x1, y2 > y1 ? y1 + CORNER : y1 - CORNER)); } } else { if (x0 - x1 > CORNER && Math.Abs(y2 - y1) > CORNER) { points.Add(new PointBase(x1 + CORNER, y1)); points.Add(new PointBase(x1, y2 > y1 ? y1 + CORNER : y1 - CORNER)); } } } } points.Add(linePoints[linePoints.Count - 1]); return points.Count % 2 == 0 ? points : linePoints; } internal static List GetDirectLine(ConnectorInfoBase source, ConnectorInfoBase sink) { List linePoints = new List(); linePoints.Add(source.MiddlePosition); linePoints.Add(sink.MiddlePosition); return linePoints; } internal static List GetConnectionLine(ConnectorInfoBase source, ConnectorInfoBase sink, bool showLastLine) { List linePoints = new List(); RectangleBase rectSource = GetRectWithMargin(source, margin); RectangleBase rectSink = GetRectWithMargin(sink, margin); PointBase startPoint = GetOffsetPoint(source, rectSource); PointBase endPoint = GetOffsetPoint(sink, rectSink); linePoints.Add(startPoint); PointBase currentPoint = startPoint; if (!rectSink.Contains(currentPoint) && !rectSource.Contains(endPoint)) { while (true) { #region source node if (IsPointVisible(currentPoint, endPoint, new RectangleBase[] { rectSource, rectSink })) { linePoints.Add(endPoint); currentPoint = endPoint; break; } PointBase neighbour = GetNearestVisibleNeighborSink(currentPoint, endPoint, sink, rectSource, rectSink); if (!double.IsNaN(neighbour.X)) { linePoints.Add(neighbour); linePoints.Add(endPoint); currentPoint = endPoint; break; } if (currentPoint == startPoint) { bool flag; PointBase n = GetNearestNeighborSource(source, endPoint, rectSource, rectSink, out flag); linePoints.Add(n); currentPoint = n; if (!IsRectVisible(currentPoint, rectSink, new RectangleBase[] { rectSource })) { PointBase n1, n2; GetOppositeCorners(source.Orientation, rectSource, out n1, out n2); if (flag) { linePoints.Add(n1); currentPoint = n1; } else { linePoints.Add(n2); currentPoint = n2; } if (!IsRectVisible(currentPoint, rectSink, new RectangleBase[] { rectSource })) { if (flag) { linePoints.Add(n2); currentPoint = n2; } else { linePoints.Add(n1); currentPoint = n1; } } } } #endregion #region sink node else // from here on we jump to the sink node { PointBase n1, n2; // neighbour corner PointBase s1, s2; // opposite corner GetNeighborCorners(sink.Orientation, rectSink, out s1, out s2); GetOppositeCorners(sink.Orientation, rectSink, out n1, out n2); bool n1Visible = IsPointVisible(currentPoint, n1, new RectangleBase[] { rectSource, rectSink }); bool n2Visible = IsPointVisible(currentPoint, n2, new RectangleBase[] { rectSource, rectSink }); if (n1Visible && n2Visible) { if (rectSource.Contains(n1)) { linePoints.Add(n2); if (rectSource.Contains(s2)) { linePoints.Add(n1); linePoints.Add(s1); } else linePoints.Add(s2); linePoints.Add(endPoint); currentPoint = endPoint; break; } if (rectSource.Contains(n2)) { linePoints.Add(n1); if (rectSource.Contains(s1)) { linePoints.Add(n2); linePoints.Add(s2); } else linePoints.Add(s1); linePoints.Add(endPoint); currentPoint = endPoint; break; } if ((Distance(n1, endPoint) <= Distance(n2, endPoint))) { linePoints.Add(n1); if (rectSource.Contains(s1)) { linePoints.Add(n2); linePoints.Add(s2); } else linePoints.Add(s1); linePoints.Add(endPoint); currentPoint = endPoint; break; } else { linePoints.Add(n2); if (rectSource.Contains(s2)) { linePoints.Add(n1); linePoints.Add(s1); } else linePoints.Add(s2); linePoints.Add(endPoint); currentPoint = endPoint; break; } } else if (n1Visible) { linePoints.Add(n1); if (rectSource.Contains(s1)) { linePoints.Add(n2); linePoints.Add(s2); } else linePoints.Add(s1); linePoints.Add(endPoint); currentPoint = endPoint; break; } else { linePoints.Add(n2); if (rectSource.Contains(s2)) { linePoints.Add(n1); linePoints.Add(s1); } else linePoints.Add(s2); linePoints.Add(endPoint); currentPoint = endPoint; break; } } #endregion } } else { linePoints.Add(endPoint); } linePoints = OptimizeLinePoints(linePoints, new RectangleBase[] { rectSource, rectSink }, source.Orientation, sink.Orientation); CheckPathEnd(source, sink, showLastLine, linePoints); //return linePoints; return AddCornerPoints(linePoints); } internal static List GetConnectionLine(ConnectorInfoBase source, PointBase sinkPoint, ConnectorOrientation preferredOrientation) { List linePoints = new List(); RectangleBase rectSource = GetRectWithMargin(source, 5); PointBase startPoint = GetOffsetPoint(source, rectSource); PointBase endPoint = sinkPoint; linePoints.Add(startPoint); PointBase currentPoint = startPoint; if (!rectSource.Contains(endPoint)) { while (true) { if (IsPointVisible(currentPoint, endPoint, new RectangleBase[] { rectSource })) { linePoints.Add(endPoint); break; } bool sideFlag; PointBase n = GetNearestNeighborSource(source, endPoint, rectSource, out sideFlag); linePoints.Add(n); currentPoint = n; if (IsPointVisible(currentPoint, endPoint, new RectangleBase[] { rectSource })) { linePoints.Add(endPoint); break; } else { PointBase n1, n2; GetOppositeCorners(source.Orientation, rectSource, out n1, out n2); if (sideFlag) linePoints.Add(n1); else linePoints.Add(n2); linePoints.Add(endPoint); break; } } } else { linePoints.Add(endPoint); } if (preferredOrientation != ConnectorOrientation.None) linePoints = OptimizeLinePoints(linePoints, new RectangleBase[] { rectSource }, source.Orientation, preferredOrientation); else linePoints = OptimizeLinePoints(linePoints, new RectangleBase[] { rectSource }, source.Orientation, GetOpositeOrientation(source.Orientation)); return linePoints; } private static List OptimizeLinePoints(List linePoints, RectangleBase[] rectangles, ConnectorOrientation sourceOrientation, ConnectorOrientation sinkOrientation) { List points = new List(); int cut = 0; for (int i = 0; i < linePoints.Count; i++) { if (i >= cut) { for (int k = linePoints.Count - 1; k > i; k--) { if (IsPointVisible(linePoints[i], linePoints[k], rectangles)) { cut = k; break; } } points.Add(linePoints[i]); } } #region Line for (int j = 0; j < points.Count - 1; j++) { if (points[j].X != points[j + 1].X && points[j].Y != points[j + 1].Y) { ConnectorOrientation orientationFrom; ConnectorOrientation orientationTo; // orientation from point if (j == 0) orientationFrom = sourceOrientation; else orientationFrom = GetOrientation(points[j], points[j - 1]); // orientation to pint if (j == points.Count - 2) orientationTo = sinkOrientation; else orientationTo = GetOrientation(points[j + 1], points[j + 2]); if ((orientationFrom == ConnectorOrientation.Left || orientationFrom == ConnectorOrientation.Right) && (orientationTo == ConnectorOrientation.Left || orientationTo == ConnectorOrientation.Right)) { double centerX = Math.Min(points[j].X, points[j + 1].X) + Math.Abs(points[j].X - points[j + 1].X) / 2; points.Insert(j + 1, new PointBase(centerX, points[j].Y)); points.Insert(j + 2, new PointBase(centerX, points[j + 2].Y)); if (points.Count - 1 > j + 3) points.RemoveAt(j + 3); return points; } if ((orientationFrom == ConnectorOrientation.Top || orientationFrom == ConnectorOrientation.Bottom) && (orientationTo == ConnectorOrientation.Top || orientationTo == ConnectorOrientation.Bottom)) { double centerY = Math.Min(points[j].Y, points[j + 1].Y) + Math.Abs(points[j].Y - points[j + 1].Y) / 2; points.Insert(j + 1, new PointBase(points[j].X, centerY)); points.Insert(j + 2, new PointBase(points[j + 2].X, centerY)); if (points.Count - 1 > j + 3) points.RemoveAt(j + 3); return points; } if ((orientationFrom == ConnectorOrientation.Left || orientationFrom == ConnectorOrientation.Right) && (orientationTo == ConnectorOrientation.Top || orientationTo == ConnectorOrientation.Bottom)) { points.Insert(j + 1, new PointBase(points[j + 1].X, points[j].Y)); return points; } if ((orientationFrom == ConnectorOrientation.Top || orientationFrom == ConnectorOrientation.Bottom) && (orientationTo == ConnectorOrientation.Left || orientationTo == ConnectorOrientation.Right)) { points.Insert(j + 1, new PointBase(points[j].X, points[j + 1].Y)); return points; } } } #endregion return points; } private static ConnectorOrientation GetOrientation(PointBase p1, PointBase p2) { if (p1.X == p2.X) { if (p1.Y >= p2.Y) return ConnectorOrientation.Bottom; else return ConnectorOrientation.Top; } else if (p1.Y == p2.Y) { if (p1.X >= p2.X) return ConnectorOrientation.Right; else return ConnectorOrientation.Left; } throw new Exception("Failed to retrieve orientation"); } /* private static Orientation GetOrientation(ConnectorOrientation sourceOrientation) { switch (sourceOrientation) { case ConnectorOrientation.Left: return Orientation.Horizontal; case ConnectorOrientation.Top: return Orientation.Vertical; case ConnectorOrientation.Right: return Orientation.Horizontal; case ConnectorOrientation.Bottom: return Orientation.Vertical; default: throw new Exception("Unknown ConnectorOrientation"); } }*/ private static PointBase GetNearestNeighborSource(ConnectorInfoBase source, PointBase endPoint, RectangleBase rectSource, RectangleBase rectSink, out bool flag) { PointBase n1, n2; // neighbors GetNeighborCorners(source.Orientation, rectSource, out n1, out n2); if (rectSink.Contains(n1)) { flag = false; return n2; } if (rectSink.Contains(n2)) { flag = true; return n1; } if ((Distance(n1, endPoint) <= Distance(n2, endPoint))) { flag = true; return n1; } else { flag = false; return n2; } } private static PointBase GetNearestNeighborSource(ConnectorInfoBase source, PointBase endPoint, RectangleBase rectSource, out bool flag) { PointBase n1, n2; // neighbors GetNeighborCorners(source.Orientation, rectSource, out n1, out n2); if ((Distance(n1, endPoint) <= Distance(n2, endPoint))) { flag = true; return n1; } else { flag = false; return n2; } } private static PointBase GetNearestVisibleNeighborSink(PointBase currentPoint, PointBase endPoint, ConnectorInfoBase sink, RectangleBase rectSource, RectangleBase rectSink) { PointBase s1, s2; // neighbors on sink side GetNeighborCorners(sink.Orientation, rectSink, out s1, out s2); bool flag1 = IsPointVisible(currentPoint, s1, new RectangleBase[] { rectSource, rectSink }); bool flag2 = IsPointVisible(currentPoint, s2, new RectangleBase[] { rectSource, rectSink }); if (flag1) // s1 visible { if (flag2) // s1 and s2 visible { if (rectSink.Contains(s1)) return s2; if (rectSink.Contains(s2)) return s1; if ((Distance(s1, endPoint) <= Distance(s2, endPoint))) return s1; else return s2; } else { return s1; } } else // s1 not visible { if (flag2) // only s2 visible { return s2; } else // s1 and s2 not visible { return new PointBase(double.NaN, double.NaN); } } } private static bool IsPointVisible(PointBase fromPoint, PointBase targetPoint, RectangleBase[] rectangles) { foreach (RectangleBase rect in rectangles) { if (RectangleIntersectsLine(rect, fromPoint, targetPoint)) return false; } return true; } private static bool IsRectVisible(PointBase fromPoint, RectangleBase targetRect, RectangleBase[] rectangles) { if (IsPointVisible(fromPoint, targetRect.TopLeft, rectangles)) return true; if (IsPointVisible(fromPoint, targetRect.TopRight, rectangles)) return true; if (IsPointVisible(fromPoint, targetRect.BottomLeft, rectangles)) return true; if (IsPointVisible(fromPoint, targetRect.BottomRight, rectangles)) return true; return false; } private static bool RectangleIntersectsLine(RectangleBase rect, PointBase startPoint, PointBase endPoint) { rect.Inflate(-1, -1); return rect.IntersectsWith(new RectangleBase(startPoint, endPoint)); } private static void GetOppositeCorners(ConnectorOrientation orientation, RectangleBase rect, out PointBase n1, out PointBase n2) { switch (orientation) { case ConnectorOrientation.Left: n1 = rect.TopRight; n2 = rect.BottomRight; break; case ConnectorOrientation.Top: n1 = rect.BottomLeft; n2 = rect.BottomRight; break; case ConnectorOrientation.Right: n1 = rect.TopLeft; n2 = rect.BottomLeft; break; case ConnectorOrientation.Bottom: n1 = rect.TopLeft; n2 = rect.TopRight; break; default: throw new Exception("No opposite corners found!"); } } private static void GetNeighborCorners(ConnectorOrientation orientation, RectangleBase rect, out PointBase n1, out PointBase n2) { switch (orientation) { case ConnectorOrientation.Left: n1 = rect.TopLeft; n2 = rect.BottomLeft; break; case ConnectorOrientation.Top: n1 = rect.TopLeft; n2 = rect.TopRight; break; case ConnectorOrientation.Right: n1 = rect.TopRight; n2 = rect.BottomRight; break; case ConnectorOrientation.Bottom: n1 = rect.BottomLeft; n2 = rect.BottomRight; break; default: throw new Exception("No neighour corners found!"); } } private static double Distance(PointBase p1, PointBase p2) { return PointBase.Subtract(p1, p2).Length; } private static RectangleBase GetRectWithMargin(ConnectorInfoBase connectorThumb, double margin) { RectangleBase rect; if (connectorThumb is FullyCreatedConnectorInfo fullyCreated) { rect = fullyCreated.DataItem.GetBounds(); } else { rect = new RectangleBase(); } rect.Inflate(margin, margin); return rect; } private static PointBase GetOffsetPoint(ConnectorInfoBase connector, RectangleBase rect) { PointBase offsetPoint = new PointBase(); switch (connector.Orientation) { case ConnectorOrientation.Left: offsetPoint = new PointBase(rect.Left, connector.MiddlePosition.Y); break; case ConnectorOrientation.Top: offsetPoint = new PointBase(connector.MiddlePosition.X, rect.Top); break; case ConnectorOrientation.Right: offsetPoint = new PointBase(rect.Right, connector.MiddlePosition.Y); break; case ConnectorOrientation.Bottom: offsetPoint = new PointBase(connector.MiddlePosition.X, rect.Bottom); break; default: break; } return offsetPoint; } private static void CheckPathEnd(ConnectorInfoBase source, ConnectorInfoBase sink, bool showLastLine, List linePoints) { if (showLastLine) { PointBase startPoint = new PointBase(0, 0); PointBase endPoint = new PointBase(0, 0); double marginPath = 10; switch (source.Orientation) { case ConnectorOrientation.Left: startPoint = new PointBase(source.MiddlePosition.X - marginPath, source.MiddlePosition.Y); break; case ConnectorOrientation.Top: startPoint = new PointBase(source.MiddlePosition.X, source.MiddlePosition.Y - marginPath); break; case ConnectorOrientation.Right: startPoint = new PointBase(source.MiddlePosition.X + marginPath, source.MiddlePosition.Y); break; case ConnectorOrientation.Bottom: startPoint = new PointBase(source.MiddlePosition.X, source.MiddlePosition.Y + marginPath); break; default: break; } switch (sink.Orientation) { case ConnectorOrientation.Left: endPoint = new PointBase(sink.MiddlePosition.X - marginPath, sink.MiddlePosition.Y); break; case ConnectorOrientation.Top: endPoint = new PointBase(sink.MiddlePosition.X, sink.MiddlePosition.Y - marginPath); break; case ConnectorOrientation.Right: endPoint = new PointBase(sink.MiddlePosition.X + marginPath, sink.MiddlePosition.Y); break; case ConnectorOrientation.Bottom: endPoint = new PointBase(sink.MiddlePosition.X, sink.MiddlePosition.Y + marginPath); break; default: break; } linePoints.Insert(0, startPoint); linePoints.Add(endPoint); } else { linePoints.Insert(0, source.MiddlePosition); linePoints.Add(sink.MiddlePosition); } } private static ConnectorOrientation GetOpositeOrientation(ConnectorOrientation connectorOrientation) { switch (connectorOrientation) { case ConnectorOrientation.Left: return ConnectorOrientation.Right; case ConnectorOrientation.Top: return ConnectorOrientation.Bottom; case ConnectorOrientation.Right: return ConnectorOrientation.Left; case ConnectorOrientation.Bottom: return ConnectorOrientation.Top; default: return ConnectorOrientation.Top; } } } }