using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using Microsoft.Msagl.Core.Geometry; using Microsoft.Msagl.Core.Geometry.Curves; using Microsoft.Msagl.Core.Layout; using Microsoft.Msagl.Layout.Layered; using Microsoft.Msagl.Routing; using Microsoft.Msagl.Routing.Visibility; namespace Microsoft.Msagl.Prototype.LayoutEditing { /// /// the router between nodes /// public class RouterBetweenTwoNodes { readonly Dictionary pointsToObstacles = new Dictionary(); VisibilityGraph _visGraph; ObstacleCalculator obstacleCalculator; /// /// the port of the edge start /// public Port SourcePort { get; private set; } /// /// the port of the edge end /// public Port TargetPort { get; private set; } const double enteringAngleBound = 10; /// /// the minimum angle between a node boundary curve and and an edge /// curve at the place where the edge curve intersects the node boundary /// public double EnteringAngleBound { get { return enteringAngleBound; } } double minimalPadding = 1; /// /// the curve should not come to the nodes closer than MinimalPaddin /// public double Padding { get { return minimalPadding; } private set { minimalPadding = value; } } /// /// we pad each node but not more than MaximalPadding /// public double LoosePadding { get; set; } [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] readonly GeometryGraph graph; internal Node Target { get; private set; } VisibilityVertex sourceVisibilityVertex; VisibilityVertex targetVisibilityVertex; VisibilityVertex TargetVisibilityVertex { get { return targetVisibilityVertex; } // set { targetVisibilityVertex = value; } } internal Node Source { get; private set; } [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal GeometryGraph Graph { get { return graph; } } Polyline Polyline { get; set; } internal static double DistanceFromPointToPolyline(Point p, Polyline poly) { double d = double.PositiveInfinity; double u; for (PolylinePoint pp = poly.StartPoint; pp.Next != null; pp = pp.Next) d = Math.Min(d, Point.DistToLineSegment(p, pp.Point, pp.Next.Point, out u)); return d; } /// /// /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Polyline")] double OffsetForPolylineRelaxing { get; set; } /// /// constructor /// /// /// /// /// public RouterBetweenTwoNodes(GeometryGraph graph, double minimalPadding, double maximalPadding, double offsetForRelaxing) { this.graph = graph; LoosePadding = maximalPadding; Padding = minimalPadding; OffsetForPolylineRelaxing = offsetForRelaxing; } /// /// Routes a spline between two graph nodes. sourcePort and targetPort define the start and end point of the resulting curve: /// startPoint=source.BoundaryCurve[sourcePort] and endPoint=target.BoundaryCurve[target]. /// /// /// if set to true then the method will try to improve the spline public void RouteEdge(Edge edge, bool takeYourTime) { Source = edge.Source; Target = edge.Target; SourcePort = edge.SourcePort; TargetPort = edge.TargetPort; CalculateObstacles(); LineSegment lineSeg = TryRouteStraightLine(); if (lineSeg != null) { Polyline = new Polyline(lineSeg.Start, lineSeg.End); edge.UnderlyingPolyline = SmoothedPolyline.FromPoints(Polyline); } else { CalculateTangentVisibilityGraph(); Polyline = GetShortestPolyline(); // ShowPolylineAndObstacles(); RelaxPolyline(); //ShowPolylineAndObstacles(); //ReducePolyline(); //ShowPolylineAndObstacles(); edge.UnderlyingPolyline = SmoothedPolyline.FromPoints(Polyline); if (takeYourTime) { TryToRemoveInflectionsAndCollinearSegs(edge.UnderlyingPolyline); SmoothCorners(edge.UnderlyingPolyline); } } edge.Curve = edge.UnderlyingPolyline.CreateCurve(); } //void ReducePolyline() { // for (PolylinePoint pp = this.Polyline.StartPoint.Next; pp.Next != null && pp.Next.Next != null;pp=pp.Next ) // pp = TryToRemoveOrDiminishSegment(pp, pp.Next); //} //PolylinePoint TryToRemoveOrDiminishSegment(PolylinePoint pp, PolylinePoint polylinePoint) { // TriangleOrientation orientation = Point.GetTriangleOrientation(pp.Prev.Point, pp.Point, pp.Next.Point); // if (orientation == Point.GetTriangleOrientation(pp.Point, pp.Next.Point, pp.Next.Next.Point)) { // Point x; // if (Point.LineLineIntersection(pp.Prev.Point, pp.Point, pp.Next.Point, pp.Next.Next.Point, out x)) { // if (orientation == Point.GetTriangleOrientation(pp.Point, x, pp.Next.Point)) { // if (!LineIntersectsTightObstacles(pp.Prev.Point, x) && !LineIntersectsTightObstacles(x, pp.Next.Next.Point)) { // PolylinePoint px = new PolylinePoint(x); // //inserting px instead of pp and pp.Next // px.Prev = pp.Prev; // pp.Prev.Next = px; // px.Next = pp.Next.Next; // pp.Next.Next.Prev = px; // return px.Prev; // } else { // for (double k = 0.5; k > 0.01; k /= 2) { // Point a = pp.Point * (1 - k) + x * k; // Point b = pp.Next.Point * (1 - k) + x * k; // if (!LineIntersectsTightObstacles(pp.Point, a) && // !LineIntersectsTightObstacles(a, b) && // !LineIntersectsTightObstacles(b, pp.Next.Point)) { // pp.Point = a; // pp.Next.Point = b; // break; // } // } // } // } // } // } // return pp; //} //bool LineIntersectsTightObstacles(Point point, Point x) { // return LineIntersectsTightObstacles(new LineSegment(point, x)); //} #if TEST_MSAGL //private void ShowPolylineAndObstaclesWithGraph() { // List ls = new List(); // foreach (Polyline poly in this.obstacleCalculator.TightObstacles) // ls.Add(poly); // foreach (Polyline poly in this.obstacleCalculator.LooseObstacles) // ls.Add(poly); // AddVisibilityGraph(ls); // ls.Add(Polyline); // SugiyamaLayoutSettings.Show(ls.ToArray()); //} #endif //pull the polyline out from the corners void RelaxPolyline() { RelaxedPolylinePoint relaxedPolylinePoint = CreateRelaxedPolylinePoints(Polyline); //ShowPolylineAndObstacles(); for (relaxedPolylinePoint = relaxedPolylinePoint.Next; relaxedPolylinePoint.Next != null; relaxedPolylinePoint = relaxedPolylinePoint.Next) RelaxPolylinePoint(relaxedPolylinePoint); } static RelaxedPolylinePoint CreateRelaxedPolylinePoints(Polyline polyline) { PolylinePoint p = polyline.StartPoint; var ret = new RelaxedPolylinePoint(p, p.Point); RelaxedPolylinePoint currentRelaxed = ret; while (p.Next != null) { p = p.Next; var r = new RelaxedPolylinePoint(p, p.Point) { Prev = currentRelaxed }; currentRelaxed.Next = r; currentRelaxed = r; } return ret; } void RelaxPolylinePoint(RelaxedPolylinePoint relaxedPoint) { for (double d = OffsetForPolylineRelaxing; !RelaxWithGivenOffset(d, relaxedPoint); d /= 2) { } } bool RelaxWithGivenOffset(double offset, RelaxedPolylinePoint relaxedPoint) { SetRelaxedPointLocation(offset, relaxedPoint); #if TEST_MSAGL //ShowPolylineAndObstacles(); #endif if (StickingSegmentDoesNotIntersectTightObstacles(relaxedPoint)) return true; PullCloserRelaxedPoint(relaxedPoint.Prev); return false; } static void PullCloserRelaxedPoint(RelaxedPolylinePoint relaxedPolylinePoint) { relaxedPolylinePoint.PolylinePoint.Point = 0.2 * relaxedPolylinePoint.OriginalPosition + 0.8 * relaxedPolylinePoint.PolylinePoint.Point; } bool StickingSegmentDoesNotIntersectTightObstacles(RelaxedPolylinePoint relaxedPoint) { return !LineIntersectsTightObstacles(new LineSegment(relaxedPoint.PolylinePoint.Point, relaxedPoint.Prev.PolylinePoint.Point)) && ( (relaxedPoint .Next == null || !LineIntersectsTightObstacles (new LineSegment (relaxedPoint . PolylinePoint . Point, relaxedPoint . Next . PolylinePoint . Point)))); } bool LineIntersectsTightObstacles(LineSegment ls) { return LineIntersectsRectangleNode(ls, obstacleCalculator.RootOfTightHierararchy); } static bool LineIntersectsRectangleNode(LineSegment ls, RectangleNode rectNode) { if (!ls.BoundingBox.Intersects(rectNode.Rectangle)) return false; if (rectNode.UserData != null) { // SugiyamaLayoutSettings.Show(ls, rectNode.UserData); return Curve.GetAllIntersections(rectNode.UserData, ls, false).Count > 0; } return LineIntersectsRectangleNode(ls, rectNode.Left) || LineIntersectsRectangleNode(ls, rectNode.Right); } static void SetRelaxedPointLocation(double offset, RelaxedPolylinePoint relaxedPoint) { bool leftTurn = Point.GetTriangleOrientation(relaxedPoint.Next.OriginalPosition, relaxedPoint.OriginalPosition, relaxedPoint.Prev.OriginalPosition) == TriangleOrientation.Counterclockwise; Point v = ((relaxedPoint.Next.OriginalPosition - relaxedPoint.Prev.OriginalPosition).Normalize() * offset).Rotate( Math.PI / 2); if (!leftTurn) v = -v; relaxedPoint.PolylinePoint.Point = relaxedPoint.OriginalPosition + v; } // #if TEST_MSAGL // [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // void ShowPolylineAndObstacles(){ // List ls = CreateListWithObstaclesAndPolyline(); // SugiyamaLayoutSettings.Show(ls.ToArray()); // } // // // [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // List CreateListWithObstaclesAndPolyline(){ // var ls = new List(); // foreach (Polyline poly in obstacleCalculator.TightObstacles) // ls.Add(poly); // foreach (Polyline poly in obstacleCalculator.LooseObstacles) // ls.Add(poly); // // ls.Add(Polyline); // return ls; // } // #endif void SmoothCorners(SmoothedPolyline edgePolyline) { Site a = edgePolyline.HeadSite; //the corner start Site b; //the corner origin Site c; //the corner other end const double mult = 1.5; while (Curve.FindCorner(a, out b, out c)) { double k = 0.5; CubicBezierSegment seg; double u, v; if (a.Previous == null) { //this will allow to the segment to start from site "a" u = 2; v = 1; } else if (c.Next == null) { u = 1; v = 2; //this will allow to the segment to end at site "c" } else { u = v = 1; } do { seg = Curve.CreateBezierSeg(k * u, k * v, a, b, c); b.PreviousBezierSegmentFitCoefficient = k * u; b.NextBezierSegmentFitCoefficient = k * v; k /= mult; } while (obstacleCalculator.ObstaclesIntersectICurve(seg)); k *= mult; //that was the last k if (k < 0.5) { //one time try a smoother seg k = 0.5 * (k + k * mult); seg = Curve.CreateBezierSeg(k * u, k * v, a, b, c); if (!obstacleCalculator.ObstaclesIntersectICurve(seg)) { b.PreviousBezierSegmentFitCoefficient = k * u; b.NextBezierSegmentFitCoefficient = k * v; } } a = b; } } void TryToRemoveInflectionsAndCollinearSegs(SmoothedPolyline underlyingPolyline) { bool progress = true; while (progress) { progress = false; for (Site s = underlyingPolyline.HeadSite; s != null && s.Next != null; s = s.Next) { if (s.Turn * s.Next.Turn < 0) progress = TryToRemoveInflectionEdge(ref s) || progress; } } } bool TryToRemoveInflectionEdge(ref Site s) { if (!obstacleCalculator.ObstaclesIntersectLine(s.Previous.Point, s.Next.Point)) { Site a = s.Previous; //forget s Site b = s.Next; a.Next = b; b.Previous = a; s = a; return true; } if (!obstacleCalculator.ObstaclesIntersectLine(s.Previous.Point, s.Next.Next.Point)) { //forget about s and s.Next Site a = s.Previous; Site b = s.Next.Next; a.Next = b; b.Previous = a; s = a; return true; } if (!obstacleCalculator.ObstaclesIntersectLine(s.Point, s.Next.Next.Point)) { //forget about s.Next Site b = s.Next.Next; s.Next = b; b.Previous = s; return true; } return false; } LineSegment TryRouteStraightLine() { var ls = new LineSegment(SourcePoint, TargetPoint); if (obstacleCalculator.ObstaclesIntersectICurve(ls)) return null; return ls; } internal Point TargetPoint { get { var tp = TargetPort as CurvePort; if (tp != null) return Target.BoundaryCurve[tp.Parameter]; return TargetPort.Location; } } internal Point SourcePoint { get { var sp = SourcePort as CurvePort; if (sp != null) return Source.BoundaryCurve[sp.Parameter]; return SourcePort.Location; } } [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] Polyline GetShortestPolyline() { var pathCalc = new SingleSourceSingleTargetShortestPathOnVisibilityGraph(_visGraph, this.sourceVisibilityVertex, TargetVisibilityVertex); var path = pathCalc.GetPath(false); var ret = new Polyline(); foreach (var v in path) ret.AddPoint(v.Point); return RemoveCollinearPoint(ret); } static Polyline RemoveCollinearPoint(Polyline ret) { for (PolylinePoint pp = ret.StartPoint.Next; pp.Next != null; pp = pp.Next) if (Point.GetTriangleOrientation(pp.Prev.Point, pp.Point, pp.Next.Point) == TriangleOrientation.Collinear) { pp.Prev.Next = pp.Next; pp.Next.Prev = pp.Prev; } return ret; } [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")] void CalculateTangentVisibilityGraph() { _visGraph = VisibilityGraph.GetVisibilityGraphForShortestPath( SourcePoint, TargetPoint, obstacleCalculator.LooseObstacles, out sourceVisibilityVertex, out targetVisibilityVertex); } void CalculateObstacles() { obstacleCalculator = new ObstacleCalculator(this); obstacleCalculator.Calculate(); foreach (Polyline poly in obstacleCalculator.TightObstacles) foreach (Point p in poly) pointsToObstacles[p] = poly; } } }