using System; using System.Collections.Generic; using System.Text; using AIStudio.Wpf.DiagramDesigner.Geometrys; namespace AIStudio.Wpf.DiagramDesigner { public static partial class PathGenerators { public static PathGeneratorResult Corner(IDiagramViewModel _, ConnectionViewModel link, PointBase[] route, PointBase source, PointBase target) { route = ConcatRouteAndSourceAndTarget(route, source, target); if (route.Length > 2) return CurveThroughPoints(route, link); if (link.IsFullConnection) route = GetRouteWithFullConnectionLine(_, link, route); else route = GetRouteWithPartConnectionLine(_, link, route); double sourceAngle = SourceMarkerAdjustement(route, link.GetSourceMarkerWidth(), link.GetSourceMarkerHeight()); double targetAngle = TargetMarkerAdjustement(route, link.GetSinkMarkerWidth(), link.GetSinkMarkerHeight()); DoShift(route, link); var paths = new string[route.Length - 1]; for (var i = 0; i < route.Length - 1; i++) { paths[i] = FormattableString.Invariant($"M {route[i].X} {route[i].Y} L {route[i + 1].X} {route[i + 1].Y}"); } return new PathGeneratorResult(paths, sourceAngle, route[0], targetAngle, route[route.Length - 1]); } private const int const_margin = 20; private static PointBase[] GetRouteWithFullConnectionLine(IDiagramViewModel _, ConnectionViewModel link, PointBase[] route) { var sourceInnerPoint = link.SourceConnectorInfoFully.IsInnerPoint; PointBase sourcePoint = link.SourceConnectorInfoFully.MiddlePosition; PointBase sinkPoint = link.SinkConnectorInfo.MiddlePosition; ConnectorOrientation sourceOrientation = link.SourceConnectorInfoFully.Orientation; ConnectorOrientation sinkOrientation = link.SinkConnectorInfoFully.Orientation; List linePoints = new List(); int margin1 = sourceInnerPoint ? 0 : const_margin; int margin2 = const_margin; RectangleBase rectSource = GetRectWithMargin(sourcePoint, margin1); RectangleBase rectSink = GetRectWithMargin(sinkPoint, margin2); PointBase startPoint = GetOffsetPoint(sourcePoint, sourceOrientation, rectSource, sourceInnerPoint); PointBase endPoint = GetOffsetPoint(sinkPoint, sinkOrientation, 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, sinkOrientation, rectSource, rectSink); if (!double.IsNaN(neighbour.X)) { linePoints.Add(neighbour); linePoints.Add(endPoint); currentPoint = endPoint; break; } if (currentPoint == startPoint) { bool flag; PointBase n = GetNearestNeighborSource(sourceOrientation, endPoint, rectSource, rectSink, out flag, sourceInnerPoint); if (linePoints.Contains(n)) { break; } linePoints.Add(n); currentPoint = n; if (!IsRectVisible(currentPoint, rectSink, new RectangleBase[] { rectSource })) { PointBase n1, n2; GetOppositeCorners(sourceOrientation, rectSource, out n1, out n2, sourceInnerPoint); 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(sinkOrientation, rectSink, out s1, out s2); GetOppositeCorners(sinkOrientation, 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 }, sourceOrientation, sinkOrientation); linePoints.Insert(0, sourcePoint); linePoints.Add(sinkPoint); return linePoints.ToArray(); } private static PointBase[] GetRouteWithPartConnectionLine(IDiagramViewModel diagramViewModel, ConnectionViewModel link, PointBase[] route) { var sourceInnerPoint = link.SourceConnectorInfoFully?.IsInnerPoint ?? false; PointBase sourcePoint = link.SourceConnectorInfo.MiddlePosition; PointBase sinkPoint = link.SinkConnectorInfo.MiddlePosition; ConnectorOrientation sourceOrientation = link.SourceConnectorInfo.Orientation; ConnectorOrientation preferredOrientation = link.SourceConnectorInfo.Orientation; List linePoints = new List(); int margin = sourceInnerPoint ? 0 : const_margin; RectangleBase rectSource = GetRectWithMargin(sourcePoint, margin); PointBase startPoint = GetOffsetPoint(sourcePoint, sourceOrientation, rectSource, sourceInnerPoint); 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(sourceOrientation, endPoint, rectSource, out sideFlag, sourceInnerPoint); linePoints.Add(n); currentPoint = n; if (IsPointVisible(currentPoint, endPoint, new RectangleBase[] { rectSource })) { linePoints.Add(endPoint); break; } else { PointBase n1, n2; GetOppositeCorners(sourceOrientation, rectSource, out n1, out n2, sourceInnerPoint); 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 }, sourceOrientation, preferredOrientation); else linePoints = OptimizeLinePoints(linePoints, new RectangleBase[] { rectSource }, sourceOrientation, GetOpositeOrientation(sourceOrientation)); linePoints.Insert(0, sourcePoint); return linePoints.ToArray(); } 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 PointBase GetNearestNeighborSource(ConnectorOrientation orientation, PointBase endPoint, RectangleBase rectSource, RectangleBase rectSink, out bool flag, bool isInnerPoint) { PointBase n1, n2; // neighbors GetNeighborCorners(orientation, rectSource, out n1, out n2, isInnerPoint); 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(ConnectorOrientation orientation, PointBase endPoint, RectangleBase rectSource, out bool flag, bool isInnerPoint) { PointBase n1, n2; // neighbors GetNeighborCorners(orientation, rectSource, out n1, out n2, isInnerPoint); if ((Distance(n1, endPoint) <= Distance(n2, endPoint))) { flag = true; return n1; } else { flag = false; return n2; } } private static PointBase GetNearestVisibleNeighborSink(PointBase currentPoint, PointBase endPoint, ConnectorOrientation orientation, RectangleBase rectSource, RectangleBase rectSink) { PointBase s1, s2; // neighbors on sink side GetNeighborCorners(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, bool isInnerPoint = false) { if (isInnerPoint) { n1 = rect.Location; n2 = rect.Location; return; } 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, bool isInnerPoint = false) { if (isInnerPoint) { n1 = rect.Location; n2 = rect.Location; return; } 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!"); n1 = rect.TopLeft; n2 = rect.BottomLeft;//ToDo break; } } private static double Distance(PointBase p1, PointBase p2) { return PointBase.Subtract(p1, p2).Length; } private static RectangleBase GetRectWithMargin(PointBase point, double margin) { RectangleBase rect = new RectangleBase(point.X, point.Y, 0, 0); rect.Inflate(margin, margin); return rect; } private static PointBase GetOffsetPoint(PointBase point, ConnectorOrientation orientation, RectangleBase rect, bool isInnerPoint = false) { PointBase offsetPoint = new PointBase(); if (isInnerPoint) { offsetPoint = new PointBase(point.X, point.Y); return offsetPoint; } switch (orientation) { case ConnectorOrientation.Left: offsetPoint = new PointBase(rect.Left, point.Y); break; case ConnectorOrientation.Top: offsetPoint = new PointBase(point.X, rect.Top); break; case ConnectorOrientation.Right: offsetPoint = new PointBase(rect.Right, point.Y); break; case ConnectorOrientation.Bottom: offsetPoint = new PointBase(point.X, rect.Bottom); break; default: break; } return offsetPoint; } 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; } } } }