using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Microsoft.Msagl.Core.DataStructures; using Microsoft.Msagl.Core.Geometry; using Microsoft.Msagl.Core.Geometry.Curves; using Microsoft.Msagl.Core.Layout; using Microsoft.Msagl.Routing; using Microsoft.Msagl.Routing.Visibility; namespace Microsoft.Msagl.Prototype.LayoutEditing { /// /// calculations with obstacles /// public class ObstacleCalculator { List looseObstacles = new List(); Set portObstacles = new Set(); RectangleNode rootOfLooseHierarchy; RectangleNode rootOfTightHierarachy; RouterBetweenTwoNodes router; LineSegment sourceFilterLine; LineSegment targetFilterLine; Set tightObstacles = new Set(); internal ObstacleCalculator(RouterBetweenTwoNodes router) { this.router = router; } internal Set TightObstacles { get { return tightObstacles; } // set { tightObstacles = value; } } internal List LooseObstacles { get { return looseObstacles; } // set { looseObstacles = value; } } internal RectangleNode RootOfTightHierararchy { get { return rootOfTightHierarachy; } private set { rootOfTightHierarachy = value; } } RectangleNode RootOfLooseHierarchy { get { return rootOfLooseHierarchy; } set { rootOfLooseHierarchy = value; } } internal LineSegment SourceFilterLine { get { return sourceFilterLine; } } internal LineSegment TargetFilterLine { get { return targetFilterLine; } // set { targetFilterLine = value; } } double EnteringAngle { get { return router.EnteringAngleBound * Math.PI / 180; } } /// /// There are two sets of obstacles: tight and loose. /// We route the shortest path between loose obstacles, and then beautify it while only taking into account tight obstacles /// /// internal void Calculate() { CreateTightObstacles(); CreateLooseObstacles(); } void CreateLooseObstacles() { RootOfLooseHierarchy = RootOfTightHierararchy.Clone(); TraverseHierarchy(RootOfLooseHierarchy, delegate(RectangleNode node) { if (node.UserData != null) { Polyline tightPolyline = node.UserData; double distance = FindMaxPaddingForTightPolyline(tightPolyline); LooseObstacles.Add( node.UserData = LoosePolylineWithFewCorners(tightPolyline, Math.Min( router.LoosePadding, distance * 0.3))); node.Rectangle = node.UserData.BoundingBox; InteractiveObstacleCalculator.UpdateRectsForParents(node); } }); } //internal void ShowRectangleNodesHierarchy(RectangleNode node) { // List ls = new List(); // FillList(ls, node); // SugiyamaLayoutSettings.Show(ls.ToArray()); //} //internal void FillList(List ls, RectangleNode node) { // if (node == null) // return; // if (node.UserData != null) // ls.Add(node.UserData); // else { // FillList(ls, node.Left); // FillList(ls, node.Right); // } //} static void TraverseHierarchy(RectangleNode node, Visitor visitor) { visitor(node); if (node.Left != null) TraverseHierarchy(node.Left, visitor); if (node.Right != null) TraverseHierarchy(node.Right, visitor); } void CreateTightObstacles() { CreateInitialTightObstacles(); List> overlappingPolylineSets; do { RemoveTightObstaclesOverlappingPortTightObstacles(); CalculateTightHierarchy(); overlappingPolylineSets = GetOverlappingSets(); foreach (var overlappingSet in overlappingPolylineSets) InsertOverlappingSet(overlappingSet); } while (overlappingPolylineSets.Count > 0); } void RemoveTightObstaclesOverlappingPortTightObstacles() { var toRemove = new List(); foreach (Polyline poly in TightObstaclesMinusPortObstacles()) foreach (ICurve portObstacle in portObstacles) if (poly.BoundingBox.Intersects(portObstacle.BoundingBox)) if (Curve.GetAllIntersections(poly, portObstacle, false).Count > 0 || OneCurveLiesInsideOfOther(poly, portObstacle)) toRemove.Add(poly); foreach (Polyline poly in toRemove) TightObstacles.Remove(poly); } IEnumerable TightObstaclesMinusPortObstacles() { foreach (Polyline p in TightObstacles) if (portObstacles.Contains(p) == false) yield return p; } void InsertOverlappingSet(Set overlappingSet) { foreach (Polyline p in overlappingSet) tightObstacles.Remove(p); var hull = new Polyline(); foreach (Point p in ConvexHull.CalculateConvexHull(EnumerateOverSetOfPolylines(overlappingSet))) hull.AddPoint(p); hull.Closed = true; //debug //List ls=new List(); //foreach(Polyline p in overlappingSet) // ls.Add(p); //ls.Add(hull); //SugiyamaLayoutSettings.Show(ls.ToArray()); //end of debug tightObstacles.Insert(hull); } IEnumerable EnumerateOverSetOfPolylines(Set pp) { foreach (Polyline poly in pp) foreach (Point p in poly) yield return p; } List> GetOverlappingSets() { PolylineGraph overlapGraph = CalculateOverlapGraph(); return ConnectedComponents(overlapGraph); } static List> ConnectedComponents(PolylineGraph overlapGraph) { var list = new List>(); var processedPolylines = new Set(); foreach (Polyline poly in overlapGraph.Nodes) { if (!processedPolylines.Contains(poly)) { Set component = GetComponent(poly, overlapGraph); if (component.Count > 1) list.Add(component); processedPolylines += component; } } return list; } static Set GetComponent(Polyline poly, PolylineGraph graph) { var ret = new Set(); ret.Insert(poly); var queue = new Queue(); queue.Enqueue(poly); while (queue.Count > 0) { foreach (Polyline p in graph.Descendents(queue.Dequeue())) { if (!ret.Contains(p)) { queue.Enqueue(p); ret.Insert(p); } } } return ret; } PolylineGraph CalculateOverlapGraph() { var graph = new PolylineGraph(); CreateEdgesUnderTwoNodes(rootOfTightHierarachy, rootOfTightHierarachy, graph); return graph; } void CalculateTightHierarchy() { var rectNodes = new List>(); foreach (Polyline polyline in TightObstacles) rectNodes.Add(CreateRectNodeOfPolyline(polyline)); RootOfTightHierararchy = RectangleNode.CreateRectangleNodeOnListOfNodes(rectNodes); } static RectangleNode CreateRectNodeOfPolyline(Polyline polyline) { return new RectangleNode(polyline, (polyline as ICurve).BoundingBox); } void CreateEdgesUnderTwoNodes(RectangleNode a, RectangleNode b, PolylineGraph overlapGraph) { //if (a.GetHashCode() < b.GetHashCode()) // return; Debug.Assert((a.UserData == null && a.Left != null && a.Right != null) || (a.UserData != null && a.Left == null && a.Right == null)); Debug.Assert((b.UserData == null && b.Left != null && b.Right != null) || (b.UserData != null && b.Left == null && b.Right == null)); if (a.Rectangle.Intersects(b.Rectangle)) { if (a.UserData != null) { if (b.UserData != null) { if (a.UserData != b.UserData) if (Curve.GetAllIntersections(a.UserData, b.UserData, false).Count > 0 || OneCurveLiesInsideOfOther(a.UserData, b.UserData)) { overlapGraph.AddEdge(a.UserData, b.UserData); overlapGraph.AddEdge(b.UserData, a.UserData); } } else { CreateEdgesUnderTwoNodes(a, b.Left, overlapGraph); CreateEdgesUnderTwoNodes(a, b.Right, overlapGraph); } } else if (b.UserData != null) { CreateEdgesUnderTwoNodes(b, a.Left, overlapGraph); CreateEdgesUnderTwoNodes(b, a.Right, overlapGraph); } else { CreateEdgesUnderTwoNodes(a.Left, b.Left, overlapGraph); CreateEdgesUnderTwoNodes(a.Left, b.Right, overlapGraph); CreateEdgesUnderTwoNodes(a.Right, b.Left, overlapGraph); CreateEdgesUnderTwoNodes(a.Right, b.Right, overlapGraph); } } } static bool OneCurveLiesInsideOfOther(ICurve polyA, ICurve polyB) { return (Curve.PointRelativeToCurveLocation(polyA.Start, polyB) != PointLocation.Outside || Curve.PointRelativeToCurveLocation(polyB.Start, polyA) != PointLocation.Outside); } /// /// /// void CreateInitialTightObstacles() { tightObstacles = new Set(); foreach (Node node in router.Graph.Nodes) { if (node == router.Source) CreatePortObstacles(router.Source, router.SourcePort, out sourceFilterLine); else if (node == router.Target) CreatePortObstacles(router.Target, router.TargetPort, out targetFilterLine); else { TightObstacles.Insert(PaddedPolylineBoundaryOfNode(node, router.Padding)); } } } void CreatePortObstacles(Node node, Port port, out LineSegment filterLine) { var bp = port as CurvePort; if (bp != null) { var padding = router.Padding; var closedCurve = node.BoundaryCurve; Curve paddingCurve = GetPaddedPolyline(closedCurve, padding).ToCurve(); Point portPoint = node.BoundaryCurve[bp.Parameter]; double length = node.BoundaryCurve.BoundingBox.Width + node.BoundaryCurve.BoundingBox.Height; double leftTipPar = GetLeftTipParam(bp.Parameter, portPoint, paddingCurve, node, length); double rightTipPar = GetRightTipParam(bp.Parameter, portPoint, paddingCurve, node, length); paddingCurve = TrimCurve(paddingCurve, rightTipPar, leftTipPar); //a simplifying hack here. I know that the parameter start from 0 and advances by 1 on every segment int n = paddingCurve.Segments.Count / 2; Debug.Assert(n > 0); Curve rightChunk = TrimCurve(paddingCurve, 0, n); Curve leftChunk = TrimCurve(paddingCurve, n + 0.8, paddingCurve.ParEnd); filterLine = new LineSegment(0.5 * (leftChunk.Start + leftChunk.End), 0.5 * (rightChunk.Start + rightChunk.End)); Polyline pol = Polyline.PolylineFromCurve(leftChunk); pol.Closed = true; portObstacles.Insert(pol); TightObstacles.Insert(pol); pol = Polyline.PolylineFromCurve(rightChunk); pol.Closed = true; portObstacles.Insert(pol); TightObstacles.Insert(pol); /* Polyline padded = CreatePaddedCurve(Anchor.PolylineAroundClosedCurve(node.BoundaryCurve), this.router.MinimalPadding); PolylinePoint polyRightBottomPoint = FindPolyPointToCutAt(node, bp.Parameter, padded); PolylinePoint nextToRightTopTip; Point rightTopTip = GetRightTopTip(node, bp.Parameter, padded, out nextToRightTopTip); PolylinePoint prevToLeftTopTip; Point leftTopTip = GetLeftTopTip(node, bp.Parameter, padded, out prevToLeftTopTip); double al = 0.25; Point rightLow = (1 - al) * polyRightBottomPoint.Point + al * polyRightBottomPoint.NextOnPolyline.Point; Point leftLow = al * polyRightBottomPoint.Point + (1 - al) * polyRightBottomPoint.NextOnPolyline.Point; filterLine = new LineSegment(0.5 * (rightTopTip + rightLow), 0.5 * (leftTopTip + leftLow)); AddRightObstacle(rightTopTip, nextToRightTopTip, polyRightBottomPoint, rightLow); AddLeftObstacle(leftLow, polyRightBottomPoint.NextOnPolyline, prevToLeftTopTip, leftTopTip); */ } else { filterLine = null; portObstacles.Insert(node.BoundaryCurve); } } /// /// /// /// /// static public Polyline GetPaddedPolyline(ICurve closedCurve, double padding) { return InteractiveObstacleCalculator.CreatePaddedPolyline(Curve.PolylineAroundClosedCurve(closedCurve), padding); } static Curve TrimCurve(Curve curve, double u, double v) { Debug.Assert(u >= curve.ParStart && u <= curve.ParEnd); Debug.Assert(v >= curve.ParStart && v <= curve.ParEnd); if (u < v) return curve.Trim(u, v) as Curve; var c = new Curve(); c.AddSegment(curve.Trim(u, curve.ParEnd) as Curve); c.AddSegment(curve.Trim(curve.ParStart, v) as Curve); return c; } double GetRightTipParam(double portParam, Point portPoint, Curve paddingCurve, Node node, double length) { bool curveIsClockwise = InteractiveObstacleCalculator.CurveIsClockwise(node.BoundaryCurve, node.Center); Point tan = curveIsClockwise ? node.BoundaryCurve.RightDerivative(portParam) : -node.BoundaryCurve.LeftDerivative(portParam); tan = (tan.Normalize() * length).Rotate(EnteringAngle); IList xs = Curve.GetAllIntersections(paddingCurve, new LineSegment(portPoint, portPoint + tan), true); Debug.Assert(xs.Count == 1); return xs[0].Par0; } double GetLeftTipParam(double portParam, Point portPoint, Curve paddingCurve, Node node, double length) { bool curveIsClockwise = InteractiveObstacleCalculator.CurveIsClockwise(node.BoundaryCurve, node.Center); Point tan = curveIsClockwise ? node.BoundaryCurve.LeftDerivative(portParam) : -node.BoundaryCurve.RightDerivative(portParam); tan = ((-tan.Normalize()) * length).Rotate(-EnteringAngle); IList xs = Curve.GetAllIntersections(paddingCurve, new LineSegment(portPoint, portPoint + tan), true); Debug.Assert(xs.Count == 1); return xs[0].Par0; } /// /// Creates a padded polyline boundary of the node. The polyline offsets at least as the padding from the node boundary. /// /// /// /// static Polyline PaddedPolylineBoundaryOfNode(Node node, double padding) { return InteractiveObstacleCalculator.CreatePaddedPolyline(Curve.PolylineAroundClosedCurve(node.BoundaryCurve), padding); } static Polyline LoosePolylineWithFewCorners(Polyline tightPolyline, double p) { if (p < ApproximateComparer.DistanceEpsilon) return tightPolyline; Polyline loosePolyline = CreateLoosePolylineOnBisectors(tightPolyline, p); return loosePolyline; } //private Polyline CutCorners(Polyline loosePolyline, Polyline tightPolyline) { // Polyline ret = new Polyline(); // ret.Closed = true; // PolylinePoint pp = loosePolyline.StartPoint; // PolylinePoint tpp=tightPolyline.StartPoint; // do { // PolylinePoint furthestVisible = GetFurthestVisible(pp, ref tpp); // ret.AddPoint(furthestVisible.Point); // pp = furthestVisible; // } // while (pp != loosePolyline.StartPoint); // System.Diagnostics.Debug.Assert(pp == loosePolyline.StartPoint); // //distangle ret.StartPoint and ret.LastPoint // return ret; //} //static PolylinePoint GetFurthestVisible(PolylinePoint pp, ref PolylinePoint tpp) { // Point pivot = pp.Point; // Point blockingPoint = tpp.NextOnPolyline.Point; // while (Point.GetTriangleOrientation(pivot, blockingPoint, pp.NextOnPolyline.Point) == TriangleOrientation.Counterclockwise) { // pp = pp.NextOnPolyline; // tpp = tpp.NextOnPolyline; // } // return pp; //} static Polyline CreateLoosePolylineOnBisectors(Polyline tightPolyline, double p) { var ret = new Polyline(); ret.AddPoint(GetStickingVertexOnBisector(tightPolyline.StartPoint, p)); var blockingPoint = new Point(); //to silence the compiler var candidate = new Point(); bool justAdded = true; for (PolylinePoint pp = tightPolyline.StartPoint.Next; pp != null; pp = pp.Next) { Point currentSticking = GetStickingVertexOnBisector(pp, p); if (justAdded) { blockingPoint = pp.Point; candidate = currentSticking; justAdded = false; } else { if (ret.Count > 1) { // SugiyamaLayoutSettings.Show(tightPolyline, ret, new LineSegment(ret.StartPoint.Point, currentSticking)); } //SugiyamaLayoutSettings.Show(new LineSegment(ret.EndPoint.Point, blockingPoint), tightPolyline, new LineSegment(ret.EndPoint.Point, currentSticking)); if (Point.GetTriangleOrientation(ret.EndPoint.Point, blockingPoint, currentSticking) != TriangleOrientation.Counterclockwise) { ret.AddPoint(candidate); // SugiyamaLayoutSettings.Show(ret, tightPolyline); justAdded = true; pp = pp.Prev; } else { candidate = currentSticking; if (Point.GetTriangleOrientation(ret.EndPoint.Point, blockingPoint, pp.Point) == TriangleOrientation.Counterclockwise) blockingPoint = pp.Point; } } } //process the last point if (!justAdded) { if (Point.GetTriangleOrientation(ret.EndPoint.Point, blockingPoint, ret.StartPoint.Point) == TriangleOrientation.Counterclockwise) { //the first point is visible, but now can we cut it if (Point.GetTriangleOrientation(ret.EndPoint.Point, blockingPoint, ret.StartPoint.Next.Point) == TriangleOrientation.Counterclockwise) ret.RemoveStartPoint(); } else { ret.AddPoint(candidate); } } else { //trying to cut away the first point if ( Point.GetTriangleOrientation(ret.EndPoint.Point, tightPolyline.StartPoint.Point, ret.StartPoint.Next.Point) == TriangleOrientation.Counterclockwise) ret.RemoveStartPoint(); else { } } ret.Closed = true; // SugiyamaLayoutSettings.Show(tightPolyline, ret); return ret; } static Point GetStickingVertexOnBisector(PolylinePoint pp, double p) { Point u = pp.Polyline.Prev(pp).Point; Point v = pp.Point; Point w = pp.Polyline.Next(pp).Point; return p * ((v - u).Normalize() + (v - w).Normalize()).Normalize() + v; } double FindMaxPaddingForTightPolyline(Polyline polyline) { var dist = double.MaxValue; var polygon = new Polygon(polyline); foreach (var poly in RootOfLooseHierarchy.GetAllLeaves().Where(p => p != polyline)) dist = Math.Min(dist, Polygon.Distance(polygon, new Polygon(poly))); // TraverseHierarchy(RootOfLooseHierarchy, delegate(RectangleNode node) { // if (node.UserData != null) // if (node.UserData != polyline) // dist = Math.Min(dist, // Polygon.Distance(polygon, // new Polygon( // node.UserData))); // }); dist = Math.Min(dist, RouterBetweenTwoNodes.DistanceFromPointToPolyline(router.SourcePoint, polyline)); dist = Math.Min(dist, RouterBetweenTwoNodes.DistanceFromPointToPolyline(router.TargetPoint, polyline)); return dist; } static bool CurvesIntersect(ICurve a, ICurve b) { return Curve.GetAllIntersections(a, b, false).Count > 0; } internal bool ObstaclesIntersectLine(Point a, Point b) { return ObstaclesIntersectICurve(new LineSegment(a, b)); } internal bool ObstaclesIntersectICurve(ICurve curve) { return CurveIntersectsRectangleNode(curve, RootOfTightHierararchy) || (SourceFilterLine != null && CurvesIntersect(curve, SourceFilterLine)) || (TargetFilterLine != null && CurvesIntersect(curve, TargetFilterLine)); } internal static bool CurveIntersectsRectangleNode(ICurve curve, RectangleNode rectNode) { Rectangle boundingBox = curve.BoundingBox; return CurveIntersectsRectangleNode(curve, ref boundingBox, rectNode); } static bool CurveIntersectsRectangleNode(ICurve curve, ref Rectangle curveBox, RectangleNode rectNode) { if (!rectNode.Rectangle.Intersects(curveBox)) return false; if (rectNode.UserData != null) return Curve.CurveCurveIntersectionOne(rectNode.UserData, curve, false) != null || Inside(rectNode.UserData, curve); Debug.Assert(rectNode.Left != null && rectNode.Right != null); return CurveIntersectsRectangleNode(curve, ref curveBox, rectNode.Left) || CurveIntersectsRectangleNode(curve, ref curveBox, rectNode.Right); } /// /// we know here that there are no intersection between "curveUnderTest" and "curve", /// We are testing that curve is inside of "curveUnderTest" /// /// /// /// static bool Inside(ICurve curveUnderTest, ICurve curve) { return Curve.PointRelativeToCurveLocation(curve.Start, curveUnderTest) == PointLocation.Inside; } #region Nested type: Visitor delegate void Visitor(RectangleNode node); #endregion /// /// /// /// /// /// /// public static bool CurveIntersectsRectangleNode(Polyline quadrilateral, RectangleNode rectangleNode, Polyline polylineToIgnore) { Rectangle boundingBox = quadrilateral.BoundingBox; return CurveIntersectsRectangleNode(quadrilateral, ref boundingBox, rectangleNode, polylineToIgnore); } static bool CurveIntersectsRectangleNode(ICurve curve, ref Rectangle curveBox, RectangleNode rectNode, Polyline polylineToIgnore) { if (!rectNode.Rectangle.Intersects(curveBox)) return false; if (rectNode.UserData != null) return rectNode.UserData != polylineToIgnore && (Curve.CurveCurveIntersectionOne(rectNode.UserData, curve, false) != null || Inside(rectNode.UserData, curve)); Debug.Assert(rectNode.Left != null && rectNode.Right != null); return CurveIntersectsRectangleNode(curve, ref curveBox, rectNode.Left, polylineToIgnore) || CurveIntersectsRectangleNode(curve, ref curveBox, rectNode.Right, polylineToIgnore); } private class PolylineGraph { private Dictionary> sourceToTargets = new Dictionary>(); internal IEnumerable Nodes { get { return sourceToTargets.Keys; } } internal void AddEdge(Polyline source, Polyline target) { List listOfEdges; if (!sourceToTargets.TryGetValue(source, out listOfEdges)) { sourceToTargets[source] = listOfEdges = new List(); } listOfEdges.Add(target); } internal IEnumerable Descendents(Polyline Polyline) { return sourceToTargets[Polyline]; } } } }