using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Msagl.Core; using Microsoft.Msagl.Core.DataStructures; using Microsoft.Msagl.Core.Geometry; using Microsoft.Msagl.Core.Geometry.Curves; using Microsoft.Msagl.Core.Layout; using Microsoft.Msagl.Layout.Initial; using Microsoft.Msagl.Layout.LargeGraphLayout; using Microsoft.Msagl.Routing.Spline.ConeSpanner; using Microsoft.Msagl.Routing.Visibility; #if TEST_MSAGL using Microsoft.Msagl.DebugHelpers; #endif namespace Microsoft.Msagl.Routing { /// /// the router between nodes /// public class InteractiveEdgeRouter : AlgorithmBase { /// /// the obstacles for routing /// public IEnumerable Obstacles { get; private set; } /// /// the minimum angle between a node boundary curve and and an edge /// curve at the place where the edge curve intersects the node boundary /// double EnteringAngleBound { get; set; } Polyline _sourceTightPolyline; Polyline SourceTightPolyline { get { return _sourceTightPolyline; } set { _sourceTightPolyline = value; } } /// /// /// Polyline SourceLoosePolyline { get; set; } Polyline targetTightPolyline; /// /// /// Polyline TargetTightPolyline { get { return targetTightPolyline; } set { targetTightPolyline = value; } } Polyline targetLoosePolyline; Polyline TargetLoosePolyline { get { return targetLoosePolyline; } set { targetLoosePolyline = value; } } //RectangleNode RootOfTightHierarchy { // get { return this.obstacleCalculator.RootOfTightHierararchy; } //} Rectangle activeRectangle = Rectangle.CreateAnEmptyBox(); VisibilityGraph visibilityGraph; internal VisibilityGraph VisibilityGraph { get { return visibilityGraph; } set { visibilityGraph = value; } } //List activeTightPolylines = new List(); List activePolygons = new List(); readonly Set alreadyAddedOrExcludedPolylines = new Set(); // Dictionary pointsToObstacles = new Dicitonary(); Port sourcePort; /// /// the port of the edge start /// public Port SourcePort { get { return sourcePort; } private set { sourcePort = value; if (sourcePort != null) { SourceTightPolyline = GetFirstHitPolyline(sourcePort.Location, ObstacleCalculator.RootOfTightHierarchy); if (sourcePort is FloatingPort) { alreadyAddedOrExcludedPolylines.Insert(SourceLoosePolyline); //we need to exclude the loose polyline around the source port from the tangent visibily graph StartPointOfEdgeRouting = SourcePort.Location; } else { var bp = (CurvePort) sourcePort; StartPointOfEdgeRouting = TakeBoundaryPortOutsideOfItsLoosePolyline(bp.Curve, bp.Parameter, SourceLoosePolyline); } } } } Port targetPort; /// /// the port of the edge end /// Port TargetPort { get { return targetPort; } set { targetPort = value; } } /// /// the curve should not come closer than Padding to the nodes /// public double TightPadding { get; set; } double loosePadding; /// /// we further pad each node but not more than LoosePadding. /// public double LoosePadding { get { return loosePadding; } internal set { loosePadding = value; if(ObstacleCalculator!=null) ObstacleCalculator.LoosePadding = value; } } VisibilityVertex _sourceVisibilityVertex; VisibilityVertex SourceVisibilityVertex { get { return _sourceVisibilityVertex; } // set { sourceVisibilityVertex = value; } } VisibilityVertex targetVisibilityVertex; VisibilityVertex TargetVisibilityVertex { get { return targetVisibilityVertex; } // set { targetVisibilityVertex = value; } } Polyline _polyline; /// /// /// double OffsetForPolylineRelaxing { get; set; } /// /// Set up the router and calculate the set of obstacles over which to route. /// /// the obstacles for routing /// obstacles are inflated by this much to find an inner boundary within which edges cannot enter /// /// obstacles are inflated again by this much to find initial /// routing but then spline smoothing is allowed to come inside this outer boundary. /// Loose padding of 0 will give sharp corners (no spline smoothing) /// if this is greater than 0 then a "cone spanner" visibility graph with be /// generated using cones of the specified angle to search for visibility edges. The cone spanner graph is /// a sparser graph than the complete visibility graph and is hence much faster to generate and route over /// but may not give strictly shortest path routes public InteractiveEdgeRouter(IEnumerable obstacles, double padding, double loosePadding, double coneSpannerAngle):this(obstacles, padding, loosePadding, coneSpannerAngle, false) {} /// /// The expected number of progress steps this algorithm will take. /// public int ExpectedProgressSteps { get; private set; } bool targetIsInsideOfSourceTightPolyline; bool sourceIsInsideOfTargetTightPolyline; internal bool UseEdgeLengthMultiplier; /// /// if set to true the algorithm will try to shortcut a shortest polyline inner points /// internal bool UseInnerPolylingShortcutting=true; /// /// if set to true the algorithm will try to shortcut a shortest polyline start and end /// internal bool UsePolylineEndShortcutting=true; internal bool AllowedShootingStraightLines = true; Dictionary> cornerTable; bool cacheCorners; /// /// An empty constructor for calling it from inside of MSAGL /// internal InteractiveEdgeRouter() { ObstacleCalculator = new InteractiveObstacleCalculator(Obstacles, TightPadding, LoosePadding, false); } Point StartPointOfEdgeRouting { get; set; } void ExtendVisibilityGraphToLocation(Point location) { if (VisibilityGraph == null) VisibilityGraph = new VisibilityGraph(); List addedPolygons = null; if (!activeRectangle.Contains(location)) { if (activeRectangle.IsEmpty) activeRectangle = new Rectangle(SourcePort.Location, location); else activeRectangle.Add(location); addedPolygons = GetAddedPolygonesAndMaybeExtendActiveRectangle(); foreach (Polygon polygon in addedPolygons) VisibilityGraph.AddHole(polygon.Polyline); } if (addedPolygons == null || addedPolygons.Count == 0) { if (targetVisibilityVertex != null) VisibilityGraph.RemoveVertex(targetVisibilityVertex); CalculateEdgeTargetVisibilityGraph(location); } else { RemovePointVisibilityGraphs(); var visibilityGraphGenerator = new InteractiveTangentVisibilityGraphCalculator(addedPolygons, activePolygons, VisibilityGraph); visibilityGraphGenerator.Run(); activePolygons.AddRange(addedPolygons); CalculateEdgeTargetVisibilityGraph(location); CalculateSourcePortVisibilityGraph(); } } void RemovePointVisibilityGraphs() { if (targetVisibilityVertex != null) VisibilityGraph.RemoveVertex(targetVisibilityVertex); if (_sourceVisibilityVertex != null) VisibilityGraph.RemoveVertex(_sourceVisibilityVertex); } void CalculateEdgeTargetVisibilityGraph(Point location) { PointVisibilityCalculator.CalculatePointVisibilityGraph(GetActivePolylines(), VisibilityGraph, location, VisibilityKind.Tangent, out targetVisibilityVertex); } void CalculateSourcePortVisibilityGraph() { PointVisibilityCalculator.CalculatePointVisibilityGraph(GetActivePolylines(), VisibilityGraph, StartPointOfEdgeRouting, VisibilityKind.Tangent, out _sourceVisibilityVertex); Debug.Assert(_sourceVisibilityVertex != null); } Point TakeBoundaryPortOutsideOfItsLoosePolyline(ICurve nodeBoundary, double parameter, Polyline loosePolyline) { Point location = nodeBoundary[parameter]; Point tangent = (nodeBoundary.LeftDerivative(parameter).Normalize() + nodeBoundary.RightDerivative(parameter).Normalize()).Normalize(); if (Point.GetTriangleOrientation(PointInsideOfConvexCurve(nodeBoundary), location, location + tangent) == TriangleOrientation.Counterclockwise) tangent = -tangent; tangent = tangent.Rotate(Math.PI/2); double len = loosePolyline.BoundingBox.Diagonal; var ls = new LineSegment(location, location + len*tangent); Point p = Curve.GetAllIntersections(ls, loosePolyline, false)[0].IntersectionPoint; Point del = tangent*(p - location).Length*0.5; //Point del = tangent * this.OffsetForPolylineRelaxing * 2; while (true) { ls = new LineSegment(location, p + del); bool foundIntersectionsOutsideOfSource = false; foreach (IntersectionInfo ii in IntersectionsOfLineAndRectangleNodeOverPolyline(ls, ObstacleCalculator.RootOfLooseHierarchy)) if (ii.Segment1 != loosePolyline) { del /= 1.5; foundIntersectionsOutsideOfSource = true; break; } if (!foundIntersectionsOutsideOfSource) break; } return ls.End; } static Point PointInsideOfConvexCurve(ICurve nodeBoundary) { return (nodeBoundary[0] + nodeBoundary[1.5])/2; //a hack !!!!!!!!!!!!!!!!!!!!!! } //Point TakeSourcePortOutsideOfLoosePolyline() { // CurvePort bp = SourcePort as CurvePort; // ICurve nodeBoundary = bp.Node.BoundaryCurve; // Point location = bp.Location; // Point tangent = (nodeBoundary.LeftDerivative(bp.Parameter).Normalize() + nodeBoundary.RightDerivative(bp.Parameter).Normalize()).Normalize(); // if (Point.GetTriangleOrientation(bp.Node.Center, location, location + tangent) == TriangleOrientation.Counterclockwise) // tangent = -tangent; // tangent = tangent.Rotate(Math.PI / 2); // double len = this.sourceLoosePolyline.BoundingBox.Diagonal; // Point portLocation = bp.Location; // LineSegment ls = new LineSegment(portLocation, portLocation + len * tangent); // Point p = Curve.GetAllIntersections(ls, this.SourceLoosePolyline, false)[0].IntersectionPoint; // Point del = tangent * this.OffsetForPolylineRelaxing * 2; // while (true) { // ls = new LineSegment(portLocation, p + del); // bool foundIntersectionsOutsideOfSource = false; // foreach (IntersectionInfo ii in IntersectionsOfLineAndRectangleNodeOverPolyline(ls, this.obstacleCalculator.RootOfLooseHierarchy)) // if (ii.Segment1 != this.SourceLoosePolyline) { // del /= 1.5; // foundIntersectionsOutsideOfSource = true; // break; // } // if (!foundIntersectionsOutsideOfSource) // break; // } // return ls.End; //} IEnumerable GetActivePolylines() { foreach (Polygon polygon in activePolygons) yield return polygon.Polyline; } List GetAddedPolygonesAndMaybeExtendActiveRectangle() { Rectangle rect = activeRectangle; var addedPolygones = new List(); bool added; do { added = false; foreach (Polyline loosePoly in ObstacleCalculator.RootOfLooseHierarchy.GetNodeItemsIntersectingRectangle(activeRectangle)) { if (!alreadyAddedOrExcludedPolylines.Contains(loosePoly)) { rect.Add(loosePoly.BoundingBox); addedPolygones.Add(new Polygon(loosePoly)); alreadyAddedOrExcludedPolylines.Insert(loosePoly); //we register the loose polyline in the set to not add it twice added = true; } } if (added) activeRectangle = rect; } while (added); return addedPolygones; } #region commented out code // List GetActivePolylines(Rectangle rectangleOfVisibilityGraph) { // return this.activeTightPolylines; //} //bool LineIntersectsTightObstacles(Point point, Point x) { // return LineIntersectsTightObstacles(new LineSegment(point, x)); //} #endregion #if TEST_MSAGL // 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); 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) { if (relaxedPoint.PolylinePoint.Prev.Prev == null && SourcePort is CurvePort && relaxedPoint.PolylinePoint.Polyline != SourceLoosePolyline) return; if (relaxedPoint.PolylinePoint.Next.Next == null && TargetPort is CurvePort && relaxedPoint.PolylinePoint.Polyline != TargetLoosePolyline) return; for (double d = OffsetForPolylineRelaxing; d > ApproximateComparer.DistanceEpsilon && !RelaxWithGivenOffset(d, relaxedPoint); d /= 2) { } } bool RelaxWithGivenOffset(double offset, RelaxedPolylinePoint relaxedPoint) { Debug.Assert(offset > ApproximateComparer.DistanceEpsilon); //otherwise we are cycling infinitely here SetRelaxedPointLocation(offset, relaxedPoint); 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 !PolylineSegmentIntersectsTightHierarchy(relaxedPoint.PolylinePoint.Point, relaxedPoint.Prev.PolylinePoint.Point) && (relaxedPoint.Next == null || !PolylineSegmentIntersectsTightHierarchy(relaxedPoint.PolylinePoint.Point, relaxedPoint.Next.PolylinePoint.Point)); } bool PolylineSegmentIntersectsTightHierarchy(Point a, Point b) { return PolylineIntersectsPolyRectangleNodeOfTightHierarchy(a, b, ObstacleCalculator.RootOfTightHierarchy); } bool PolylineIntersectsPolyRectangleNodeOfTightHierarchy(Point a, Point b, RectangleNode rect) { return PolylineIntersectsPolyRectangleNodeOfTightHierarchy(new LineSegment(a, b), rect); } bool PolylineIntersectsPolyRectangleNodeOfTightHierarchy(LineSegment ls, RectangleNode rect) { if (!ls.BoundingBox.Intersects(rect.Rectangle)) return false; if (rect.UserData != null) { foreach (IntersectionInfo ii in Curve.GetAllIntersections(ls, rect.UserData, false)) { if (ii.Segment1 != SourceTightPolyline && ii.Segment1 != TargetTightPolyline) return true; if (ii.Segment1 == SourceTightPolyline && SourcePort is CurvePort) return true; if (ii.Segment1 == TargetTightPolyline && TargetPort is CurvePort) return true; } return false; } return PolylineIntersectsPolyRectangleNodeOfTightHierarchy(ls, rect.Left) || PolylineIntersectsPolyRectangleNodeOfTightHierarchy(ls, rect.Right); } internal static List IntersectionsOfLineAndRectangleNodeOverPolyline(LineSegment ls, RectangleNode rectNode) { var ret = new List(); IntersectionsOfLineAndRectangleNodeOverPolyline(ls, rectNode, ret); return ret; } static void IntersectionsOfLineAndRectangleNodeOverPolyline(LineSegment ls, RectangleNode rectNode, List listOfIntersections) { if (rectNode == null) return; if (!ls.BoundingBox.Intersects(rectNode.Rectangle)) return; if (rectNode.UserData != null) { listOfIntersections.AddRange(Curve.GetAllIntersections(ls, rectNode.UserData, true)); return; } IntersectionsOfLineAndRectangleNodeOverPolyline(ls, rectNode.Left, listOfIntersections); IntersectionsOfLineAndRectangleNodeOverPolyline(ls, rectNode.Right, listOfIntersections); } bool LineCanBeAcceptedForRouting(LineSegment ls) { bool sourceIsFloating = SourcePort is FloatingPort; bool targetIsFloating = TargetPort is FloatingPort; if (!sourceIsFloating && !targetIsInsideOfSourceTightPolyline) if (!InsideOfTheAllowedConeOfBoundaryPort(ls.End, SourcePort as CurvePort)) return false; if (!targetIsFloating && TargetPort != null && !sourceIsInsideOfTargetTightPolyline) if (!InsideOfTheAllowedConeOfBoundaryPort(ls.Start, TargetPort as CurvePort)) return false; List xx = IntersectionsOfLineAndRectangleNodeOverPolyline(ls, ObstacleCalculator. RootOfTightHierarchy); foreach (IntersectionInfo ii in xx) { if (ii.Segment1 == SourceTightPolyline) continue; if (ii.Segment1 == targetTightPolyline) continue; return false; } return true; } bool InsideOfTheAllowedConeOfBoundaryPort(Point pointToTest, CurvePort port) { ICurve boundaryCurve = port.Curve; bool curveIsClockwise = InteractiveObstacleCalculator.CurveIsClockwise(boundaryCurve, PointInsideOfConvexCurve( boundaryCurve)); Point portLocation = port.Location; Point pointOnTheRightConeSide = GetPointOnTheRightBoundaryPortConeSide(portLocation, boundaryCurve, curveIsClockwise, port.Parameter); Point pointOnTheLeftConeSide = GetPointOnTheLeftBoundaryPortConeSide(portLocation, boundaryCurve, curveIsClockwise, port.Parameter); return Point.GetTriangleOrientation(portLocation, pointOnTheRightConeSide, pointToTest) != TriangleOrientation.Clockwise && Point.GetTriangleOrientation(portLocation, pointToTest, pointOnTheLeftConeSide) != TriangleOrientation.Clockwise; } Point GetPointOnTheRightBoundaryPortConeSide(Point portLocation, ICurve boundaryCurve, bool curveIsClockwise, double portParam) { Point tan = curveIsClockwise ? boundaryCurve.RightDerivative(portParam) : -boundaryCurve.LeftDerivative(portParam); return portLocation + tan.Rotate(EnteringAngleBound); } Point GetPointOnTheLeftBoundaryPortConeSide(Point portLocation, ICurve boundaryCurve, bool curveIsClockwise, double portParam) { Point tan = curveIsClockwise ? -boundaryCurve.LeftDerivative(portParam) : boundaryCurve.RightDerivative(portParam); return portLocation + tan.Rotate(-EnteringAngleBound); } 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")] // // ReSharper disable UnusedMember.Local // internal void ShowPolylineAndObstacles(params ICurve[] curves) // { // // ReSharper restore UnusedMember.Local // IEnumerable ls = GetDebugCurves(curves); // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(ls); // } // // [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // IEnumerable GetDebugCurves(params ICurve[] curves) // { // var ls = CreateListWithObstaclesAndPolyline(curves); // //ls.AddRange(this.VisibilityGraph.Edges.Select(e => new DebugCurve(100,0.1, e is TollFreeVisibilityEdge?"red":"green", new LineSegment(e.SourcePoint, e.TargetPoint)))); // if (_sourceVisibilityVertex != null) // ls.Add(new DebugCurve("red", CurveFactory.CreateDiamond(4, 4, _sourceVisibilityVertex.Point))); // if (targetVisibilityVertex != null) // ls.Add(new DebugCurve("purple", new Ellipse(4, 4, targetVisibilityVertex.Point))); // var anywerePort=targetPort as HookUpAnywhereFromInsidePort; // if (anywerePort != null) // ls.Add(new DebugCurve("purple", anywerePort.LoosePolyline)); // return ls; // } // // // [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // List CreateListWithObstaclesAndPolyline(params ICurve[] curves) // { // var ls = new List(ObstacleCalculator.RootOfLooseHierarchy.GetAllLeaves().Select(e => new DebugCurve(100,0.01, "green", e))); // ls.AddRange(curves.Select(c=>new DebugCurve(100,0.01,"red", c))); // ls.AddRange(ObstacleCalculator.RootOfTightHierarchy.GetAllLeaves().Select(e => new DebugCurve(100, 0.01, "blue", e))); // // // ls.AddRange(visibilityGraph.Edges.Select(e => (ICurve) new LineSegment(e.SourcePoint, e.TargetPoint))); // if (_polyline != null) // ls.Add(new DebugCurve(100, 0.03, "blue",_polyline)); // return ls; // } // #endif /// /// smoothing the corners of the polyline /// /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Polyline")] public void SmoothCorners(SmoothedPolyline edgePolyline) { ValidateArg.IsNotNull(edgePolyline, "edgePolyline"); Site a = edgePolyline.HeadSite; //the corner start Site b; //the corner origin Site c; //the corner other end while (Curve.FindCorner(a, out b, out c)) a = SmoothOneCorner(a, c, b); } Site SmoothOneCorner(Site a, Site c, Site b) { if (CacheCorners) { double p, n; if (FindCachedCorner(a, b, c, out p, out n)) { b.PreviousBezierSegmentFitCoefficient = p; b.NextBezierSegmentFitCoefficient = n; return b; } } const double mult = 1.5; const double kMin = 0.01; 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 > kMin); k *= mult; //that was the last k if (k < 0.5 && k > kMin) { //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; } } if (CacheCorners) CacheCorner(a, b, c); return b; } internal int foundCachedCorners; bool FindCachedCorner(Site a, Site b, Site c, out double prev, out double next) { Corner corner=new Corner(a.Point,b.Point,c.Point); Tuple prevNext; if (cornerTable.TryGetValue(corner, out prevNext)) { if (a.Point == corner.a) { prev = prevNext.Item1; next = prevNext.Item2; } else { prev = prevNext.Item2; next = prevNext.Item1; } foundCachedCorners++; return true; } prev = next = 0; return false; } void CacheCorner(Site a, Site b, Site c) { cornerTable[new Corner(a.Point,b.Point,c.Point)]=new Tuple(b.PreviousBezierSegmentFitCoefficient,b.NextBezierSegmentFitCoefficient); } /// /// is set to true will cache three points defining the corner /// to avoid obstacle avoidance calculation /// public bool CacheCorners { get { return cacheCorners; } set { cacheCorners = value; if (cacheCorners) cornerTable = new Dictionary>(); else { if (cornerTable != null) cornerTable.Clear(); } } } /// /// /// /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Polyline")] public void TryToRemoveInflectionsAndCollinearSegments(SmoothedPolyline underlyingPolyline) { ValidateArg.IsNotNull(underlyingPolyline, "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; } //internal Point TargetPoint { // get { // CurvePort tp = this.TargetPort as CurvePort; // if (tp != null) // return this.Target.BoundaryCurve[tp.Parameter]; // else // return (this.TargetPort as FloatingPort).Location; // } //} //internal Point SourcePoint { // get { // CurvePort sp = this.SourcePort as CurvePort; // if (sp != null) // return this.Source.BoundaryCurve[sp.Parameter]; // else // return (this.SourcePort as FloatingPort).Location; // } //} Polyline GetShortestPolyline(VisibilityVertex sourceVisVertex, VisibilityVertex _targetVisVertex) { CleanTheGraphForShortestPath(); var pathCalc = new SingleSourceSingleTargetShortestPathOnVisibilityGraph(this.visibilityGraph, sourceVisVertex, _targetVisVertex); IEnumerable path = pathCalc.GetPath(UseEdgeLengthMultiplier); if (path == null) { //ShowIsPassable(_sourceVisibilityVertex, _targetVisVertex); return null; } Debug.Assert(path.First() == sourceVisVertex && path.Last() == _targetVisVertex); var ret = new Polyline(); foreach (VisibilityVertex v in path) ret.AddPoint(v.Point); return RemoveCollinearVertices(ret); } // #if TEST_MSAGL // [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // private void ShowIsPassable(VisibilityVertex sourceVisVertex, VisibilityVertex targetVisVertex) // { // var dd = new List( // visibilityGraph.Edges.Select( // e => // new DebugCurve(100, 0.5, e.IsPassable == null || e.IsPassable() ? "green" : "red", // new LineSegment(e.SourcePoint, e.TargetPoint)))); // if(sourceVisVertex!=null) // dd.Add(new DebugCurve(CurveFactory.CreateDiamond(3, 3, sourceVisVertex.Point))); // if(targetVisVertex!=null) // dd.Add(new DebugCurve(CurveFactory.CreateEllipse(3, 3, targetVisVertex.Point))); // // if (Obstacles != null) // dd.AddRange(Obstacles.Select(o => new DebugCurve(o))); // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(dd); // } // #endif void CleanTheGraphForShortestPath() { visibilityGraph.ClearPrevEdgesTable(); } internal static Polyline RemoveCollinearVertices(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; } /// /// returns true if the nodes overlap or just positioned too close /// public bool OverlapsDetected { get { return ObstacleCalculator.OverlapsDetected; } } /// /// public double ConeSpannerAngle { get; set; } internal RectangleNode TightHierarchy { get { return ObstacleCalculator.RootOfTightHierarchy; } set { ObstacleCalculator.RootOfTightHierarchy = value; } } internal RectangleNode LooseHierarchy { get { return ObstacleCalculator.RootOfLooseHierarchy; } set { ObstacleCalculator.RootOfLooseHierarchy = value; } } internal bool UseSpanner { get; set; } void CalculateObstacles() { ObstacleCalculator = new InteractiveObstacleCalculator(Obstacles, TightPadding, LoosePadding, IgnoreTightPadding); ObstacleCalculator.Calculate(); } // int count; /// /// /// /// /// public EdgeGeometry RouteEdgeToLocation(Point targetLocation) { TargetPort = new FloatingPort((ICurve) null, targetLocation); //otherwise route edge to a port would be called TargetTightPolyline = null; TargetLoosePolyline = null; var edgeGeometry = new EdgeGeometry(); var ls = new LineSegment(SourcePort.Location, targetLocation); if (LineCanBeAcceptedForRouting(ls)) { _polyline = new Polyline(); _polyline.AddPoint(ls.Start); _polyline.AddPoint(ls.End); edgeGeometry.SmoothedPolyline = SmoothedPolyline.FromPoints(_polyline); edgeGeometry.Curve = edgeGeometry.SmoothedPolyline.CreateCurve(); return edgeGeometry; } //can we do with just two line segments? if (SourcePort is CurvePort) { ls = new LineSegment(StartPointOfEdgeRouting, targetLocation); if ( IntersectionsOfLineAndRectangleNodeOverPolyline(ls, ObstacleCalculator.RootOfTightHierarchy).Count == 0) { _polyline = new Polyline(); _polyline.AddPoint(SourcePort.Location); _polyline.AddPoint(ls.Start); _polyline.AddPoint(ls.End); //RelaxPolyline(); edgeGeometry.SmoothedPolyline = SmoothedPolyline.FromPoints(_polyline); edgeGeometry.Curve = edgeGeometry.SmoothedPolyline.CreateCurve(); return edgeGeometry; } } ExtendVisibilityGraphToLocation(targetLocation); _polyline = GetShortestPolyline(SourceVisibilityVertex, TargetVisibilityVertex); RelaxPolyline(); if (SourcePort is CurvePort) _polyline.PrependPoint(SourcePort.Location); edgeGeometry.SmoothedPolyline = SmoothedPolyline.FromPoints(_polyline); edgeGeometry.Curve = edgeGeometry.SmoothedPolyline.CreateCurve(); return edgeGeometry; } /// /// routes the edge to the port /// /// /// /// if true will smooth the edge avoiding the obstacles, will take more time /// /// [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "3#"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Polyline")] public ICurve RouteEdgeToPort(Port edgeTargetPort, Polyline portLoosePolyline, bool smooth, out SmoothedPolyline smoothedPolyline) { ValidateArg.IsNotNull(edgeTargetPort, "edgeTargetToPort"); if (!ObstacleCalculator.IsEmpty()) { TargetPort = edgeTargetPort; TargetTightPolyline = GetFirstHitPolyline(edgeTargetPort.Location, ObstacleCalculator.RootOfTightHierarchy); Debug.Assert(targetTightPolyline != null); var bp = edgeTargetPort as CurvePort; if (bp != null) return RouteEdgeToBoundaryPort(portLoosePolyline, smooth, out smoothedPolyline); return RouteEdgeToFloatingPortOfNode(portLoosePolyline, smooth, out smoothedPolyline); } if (sourcePort != null && targetPort != null) { smoothedPolyline = SmoothedPolylineFromTwoPoints(sourcePort.Location, targetPort.Location); return new LineSegment(sourcePort.Location, targetPort.Location); } smoothedPolyline = null; return null; } SmoothedPolyline SmoothedPolylineFromTwoPoints(Point s, Point e) { _polyline = new Polyline(); _polyline.AddPoint(s); _polyline.AddPoint(e); return SmoothedPolyline.FromPoints(_polyline); } ICurve RouteEdgeToFloatingPortOfNode(Polyline portLoosePolyline, bool smooth, out SmoothedPolyline smoothedPolyline) { if (sourcePort is FloatingPort) return RouteFromFloatingPortToFloatingPort(portLoosePolyline, smooth, out smoothedPolyline); return RouteFromBoundaryPortToFloatingPort(portLoosePolyline, smooth, out smoothedPolyline); } ICurve RouteFromBoundaryPortToFloatingPort(Polyline targetPortLoosePolyline, bool smooth, out SmoothedPolyline polyline) { Point sourcePortLocation = SourcePort.Location; Point targetPortLocation = targetPort.Location; var ls = new LineSegment(sourcePortLocation, targetPortLocation); if (LineCanBeAcceptedForRouting(ls)) { polyline = SmoothedPolylineFromTwoPoints(ls.Start, ls.End); return ls; } if (!targetIsInsideOfSourceTightPolyline) { //try a variant with two segments Point takenOutPoint = TakeBoundaryPortOutsideOfItsLoosePolyline(SourcePort.Curve, ((CurvePort) SourcePort).Parameter, SourceLoosePolyline); ls = new LineSegment(takenOutPoint, targetPortLocation); if (LineAvoidsTightHierarchy(ls, targetPortLoosePolyline)) { polyline = SmoothedPolylineFromTwoPoints(ls.Start, ls.End); return ls; } } //we need to route throw the visibility graph ExtendVisibilityGraphToLocationOfTargetFloatingPort(targetPortLoosePolyline); _polyline = GetShortestPolyline(SourceVisibilityVertex, TargetVisibilityVertex); Polyline tmp = SourceTightPolyline; if (!targetIsInsideOfSourceTightPolyline) //this is done to avoid shorcutting through the source tight polyline SourceTightPolyline = null; TryShortcutPolyline(); SourceTightPolyline = tmp; RelaxPolyline(); _polyline.PrependPoint(sourcePortLocation); return SmoothCornersAndReturnCurve(smooth, out polyline); } ICurve SmoothCornersAndReturnCurve(bool smooth, out SmoothedPolyline smoothedPolyline) { smoothedPolyline = SmoothedPolyline.FromPoints(_polyline); if (smooth) SmoothCorners(smoothedPolyline); return smoothedPolyline.CreateCurve(); } ICurve RouteFromFloatingPortToFloatingPort(Polyline portLoosePolyline, bool smooth, out SmoothedPolyline smoothedPolyline) { Point targetPortLocation = TargetPort.Location; var ls = new LineSegment(StartPointOfEdgeRouting, targetPortLocation); if ( AllowedShootingStraightLines && LineAvoidsTightHierarchy(ls, SourceTightPolyline, targetTightPolyline) ) { smoothedPolyline = SmoothedPolylineFromTwoPoints(ls.Start, ls.End); return ls; } //we need to route through the visibility graph ExtendVisibilityGraphToLocationOfTargetFloatingPort(portLoosePolyline); _polyline = GetShortestPolyline(SourceVisibilityVertex, TargetVisibilityVertex); if (_polyline == null) { smoothedPolyline = null; return null; } if (UseSpanner) TryShortcutPolyline(); RelaxPolyline(); smoothedPolyline = SmoothedPolyline.FromPoints(_polyline); return SmoothCornersAndReturnCurve(smooth, out smoothedPolyline); } void TryShortcutPolyline() { if(UseInnerPolylingShortcutting) while (ShortcutPolylineOneTime()) {} if (UsePolylineEndShortcutting) TryShortCutThePolylineEnds(); } void TryShortCutThePolylineEnds() { TryShortcutPolylineStart(); TryShortcutPolylineEnd(); } void TryShortcutPolylineEnd() { PolylinePoint a = _polyline.EndPoint; PolylinePoint b = a.Prev; if (b == null) return; PolylinePoint c = b.Prev; if (c == null) return; Point m = 0.5*(b.Point + c.Point); if (LineAvoidsTightHierarchy(a.Point, m, _sourceTightPolyline, targetTightPolyline)) { var p = new PolylinePoint(m) {Next = a, Prev = c}; a.Prev = p; c.Next = p; } } void TryShortcutPolylineStart() { PolylinePoint a = _polyline.StartPoint; PolylinePoint b = a.Next; if (b == null) return; PolylinePoint c = b.Next; if (c == null) return; Point m = 0.5*(b.Point + c.Point); if (LineAvoidsTightHierarchy(a.Point, m, _sourceTightPolyline, targetTightPolyline)) { var p = new PolylinePoint(m) {Prev = a, Next = c}; a.Next = p; c.Prev = p; } } bool ShortcutPolylineOneTime() { bool ret = false; for (PolylinePoint pp = _polyline.StartPoint; pp.Next != null && pp.Next.Next != null; pp = pp.Next) ret |= TryShortcutPolyPoint(pp); return ret; } bool TryShortcutPolyPoint(PolylinePoint pp) { if (LineAvoidsTightHierarchy(new LineSegment(pp.Point, pp.Next.Next.Point), SourceTightPolyline, targetTightPolyline)) { // if (debug) // ShowPolylineAndObstaclesWithLineSeg(new LineSegment(pp.Point, pp.Next.Next.Point)); //remove pp.Next pp.Next = pp.Next.Next; pp.Next.Prev = pp; return true; } return false; } /* void ShowPolylineAndObstaclesWithLineSeg(LineSegment lineSegment) { var ls = GetDebugCurves(); ls.Add(new DebugCurve(2,"pink", lineSegment)); LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(ls); } */ void ExtendVisibilityGraphToLocationOfTargetFloatingPort(Polyline portLoosePolyline) { if (VisibilityGraph == null) VisibilityGraph = new VisibilityGraph(); List addedPolygons = null; Point targetLocation = targetPort.Location; if (!activeRectangle.Contains(targetLocation)) { if (activeRectangle.IsEmpty) activeRectangle = new Rectangle(SourcePort.Location, targetLocation); else activeRectangle.Add(targetLocation); addedPolygons = GetAddedPolygonesAndMaybeExtendActiveRectangle(); foreach (Polygon polygon in addedPolygons) VisibilityGraph.AddHole(polygon.Polyline); } if (addedPolygons == null) { if (targetVisibilityVertex != null) VisibilityGraph.RemoveVertex(targetVisibilityVertex); CalculateEdgeTargetVisibilityGraphForFloatingPort(targetLocation, portLoosePolyline); if (SourceVisibilityVertex == null) CalculateSourcePortVisibilityGraph(); } else { RemovePointVisibilityGraphs(); var visibilityGraphGenerator = new InteractiveTangentVisibilityGraphCalculator(addedPolygons, activePolygons, VisibilityGraph); visibilityGraphGenerator.Run(); activePolygons.AddRange(addedPolygons); CalculateEdgeTargetVisibilityGraphForFloatingPort(targetLocation, portLoosePolyline); CalculateSourcePortVisibilityGraph(); } } void CalculateEdgeTargetVisibilityGraphForFloatingPort(Point targetLocation, Polyline targetLoosePoly) { if (UseSpanner) targetVisibilityVertex = AddTransientVisibilityEdgesForPort(targetLocation, targetLoosePoly); else PointVisibilityCalculator.CalculatePointVisibilityGraph( GetActivePolylinesWithException(targetLoosePoly), VisibilityGraph, targetLocation, VisibilityKind.Tangent, out targetVisibilityVertex); } VisibilityVertex AddTransientVisibilityEdgesForPort(Point point, IEnumerable loosePoly) { VisibilityVertex v = GetVertex(point); if (v != null) return v; v = visibilityGraph.AddVertex(point); if (loosePoly != null) //if the edges have not been calculated do it in a quick and dirty mode foreach (Point p in loosePoly) visibilityGraph.AddEdge(point, p, ((a, b) => new TollFreeVisibilityEdge(a, b))); else { PointVisibilityCalculator.CalculatePointVisibilityGraph(GetActivePolylines(), VisibilityGraph, point, VisibilityKind.Tangent, out v); Debug.Assert(v != null); } return v; } VisibilityVertex GetVertex(Point point) { VisibilityVertex v = visibilityGraph.FindVertex(point); if (v == null && LookForRoundedVertices) v = visibilityGraph.FindVertex(ApproximateComparer.Round(point)); return v; } internal bool LookForRoundedVertices { get; set; } internal InteractiveObstacleCalculator ObstacleCalculator { get; set; } /// /// /// /// /// /// /// public InteractiveEdgeRouter(IEnumerable obstacles, double padding, double loosePadding, double coneSpannerAngle, bool ignoreTightPadding) { IgnoreTightPadding = ignoreTightPadding; EnteringAngleBound = 80 * Math.PI / 180; TightPadding = padding; LoosePadding = loosePadding; OffsetForPolylineRelaxing = 0.75 * padding; if (coneSpannerAngle > 0) { Debug.Assert(coneSpannerAngle > Math.PI / 180); Debug.Assert(coneSpannerAngle <= 90 * Math.PI / 180); UseSpanner = true; ExpectedProgressSteps = ConeSpanner.GetTotalSteps(coneSpannerAngle); } else { ExpectedProgressSteps = obstacles.Count(); } ConeSpannerAngle = coneSpannerAngle; Obstacles = obstacles; CalculateObstacles(); } internal bool IgnoreTightPadding {get;set;} IEnumerable GetActivePolylinesWithException(Polyline targetLoosePoly) { return from polygon in activePolygons where polygon.Polyline != targetLoosePoly select polygon.Polyline; } ICurve RouteEdgeToBoundaryPort(Polyline portLoosePolyline, bool smooth, out SmoothedPolyline smoothedPolyline) { TargetLoosePolyline = portLoosePolyline; if (sourcePort is FloatingPort) return RouteFromFloatingPortToBoundaryPort(smooth, out smoothedPolyline); return RouteFromBoundaryPortToBoundaryPort(smooth, out smoothedPolyline); } ICurve RouteFromBoundaryPortToBoundaryPort(bool smooth, out SmoothedPolyline smoothedPolyline) { Point sourcePortLocation = SourcePort.Location; ICurve curve; Point targetPortLocation = targetPort.Location; var ls = new LineSegment(sourcePortLocation, targetPortLocation); if (LineCanBeAcceptedForRouting(ls)) { _polyline = new Polyline(); _polyline.AddPoint(ls.Start); _polyline.AddPoint(ls.End); smoothedPolyline = SmoothedPolylineFromTwoPoints(ls.Start,ls.End); curve = SmoothedPolyline.FromPoints(_polyline).CreateCurve(); } else { //try three variants with two segments Point takenOutPoint = TakeBoundaryPortOutsideOfItsLoosePolyline(targetPort.Curve, ((CurvePort) targetPort).Parameter, TargetLoosePolyline); ls = new LineSegment(sourcePortLocation, takenOutPoint); if (InsideOfTheAllowedConeOfBoundaryPort(takenOutPoint, SourcePort as CurvePort) && LineAvoidsTightHierarchy(ls, _sourceTightPolyline)) { _polyline = new Polyline(); _polyline.AddPoint(ls.Start); _polyline.AddPoint(ls.End); _polyline.AddPoint(targetPortLocation); curve = SmoothCornersAndReturnCurve(smooth, out smoothedPolyline); } else { ls = new LineSegment(StartPointOfEdgeRouting, targetPortLocation); if (InsideOfTheAllowedConeOfBoundaryPort(StartPointOfEdgeRouting, TargetPort as CurvePort) && LineAvoidsTightHierarchy(ls)) { _polyline = new Polyline(); _polyline.AddPoint(sourcePortLocation); _polyline.AddPoint(ls.Start); _polyline.AddPoint(ls.End); curve = SmoothCornersAndReturnCurve(smooth, out smoothedPolyline); } else { // we still can make the polyline with two segs when the port sticking segs are intersecting Point x; if (LineSegment.Intersect(sourcePortLocation, StartPointOfEdgeRouting, targetPortLocation, takenOutPoint, out x)) { _polyline = new Polyline(); _polyline.AddPoint(sourcePortLocation); _polyline.AddPoint(x); _polyline.AddPoint(targetPortLocation); curve = SmoothCornersAndReturnCurve(smooth, out smoothedPolyline); } else if (ApproximateComparer.Close(StartPointOfEdgeRouting, takenOutPoint)) { _polyline = new Polyline(); _polyline.AddPoint(sourcePortLocation); _polyline.AddPoint(takenOutPoint); _polyline.AddPoint(targetPortLocation); curve = SmoothCornersAndReturnCurve(smooth, out smoothedPolyline); } else if (LineAvoidsTightHierarchy(new LineSegment(StartPointOfEdgeRouting, takenOutPoint))) { //can we do three segments? _polyline = new Polyline(); _polyline.AddPoint(sourcePortLocation); _polyline.AddPoint(StartPointOfEdgeRouting); _polyline.AddPoint(takenOutPoint); _polyline.AddPoint(targetPortLocation); curve = SmoothCornersAndReturnCurve(smooth, out smoothedPolyline); } else { ExtendVisibilityGraphToTargetBoundaryPort(takenOutPoint); _polyline = GetShortestPolyline(SourceVisibilityVertex, TargetVisibilityVertex); Polyline tmpTargetTight; Polyline tmpSourceTight = HideSourceTargetTightsIfNeeded(out tmpTargetTight); TryShortcutPolyline(); RecoverSourceTargetTights(tmpSourceTight, tmpTargetTight); RelaxPolyline(); _polyline.PrependPoint(sourcePortLocation); _polyline.AddPoint(targetPortLocation); curve = SmoothCornersAndReturnCurve(smooth, out smoothedPolyline); } } } } return curve; } void RecoverSourceTargetTights(Polyline tmpSourceTight, Polyline tmpTargetTight) { SourceTightPolyline = tmpSourceTight; TargetTightPolyline = tmpTargetTight; } Polyline HideSourceTargetTightsIfNeeded(out Polyline tmpTargetTight) { Polyline tmpSourceTight = SourceTightPolyline; tmpTargetTight = TargetTightPolyline; SourceTightPolyline = TargetTightPolyline = null; return tmpSourceTight; } bool LineAvoidsTightHierarchy(LineSegment lineSegment) { return IntersectionsOfLineAndRectangleNodeOverPolyline(lineSegment, ObstacleCalculator.RootOfTightHierarchy). Count == 0; } ICurve RouteFromFloatingPortToBoundaryPort(bool smooth, out SmoothedPolyline smoothedPolyline) { Point targetPortLocation = targetPort.Location; LineSegment ls; if (InsideOfTheAllowedConeOfBoundaryPort(sourcePort.Location, (CurvePort)targetPort)) { ls = new LineSegment(SourcePort.Location, targetPortLocation); if (LineCanBeAcceptedForRouting(ls)) { smoothedPolyline = SmoothedPolylineFromTwoPoints(ls.Start, ls.End); return ls; } } Point takenOutTargetPortLocation = TakeBoundaryPortOutsideOfItsLoosePolyline(TargetPort.Curve, ((CurvePort) TargetPort). Parameter, TargetLoosePolyline); //can we do with just two line segments? ls = new LineSegment(SourcePort.Location, takenOutTargetPortLocation); if (LineAvoidsTightHierarchy(ls, _sourceTightPolyline)) { _polyline=new Polyline(ls.Start, ls.End, targetPortLocation); smoothedPolyline = SmoothedPolyline.FromPoints(_polyline); return smoothedPolyline.CreateCurve(); } ExtendVisibilityGraphToTargetBoundaryPort(takenOutTargetPortLocation); _polyline = GetShortestPolyline(SourceVisibilityVertex, TargetVisibilityVertex); RelaxPolyline(); _polyline.AddPoint(targetPortLocation); return SmoothCornersAndReturnCurve(smooth, out smoothedPolyline); } bool LineAvoidsTightHierarchy(LineSegment ls, Polyline polylineToExclude) { bool lineIsGood = true; foreach (IntersectionInfo ii in IntersectionsOfLineAndRectangleNodeOverPolyline(ls, ObstacleCalculator.RootOfTightHierarchy)) if (ii.Segment1 != polylineToExclude) { lineIsGood = false; break; } return lineIsGood; } bool LineAvoidsTightHierarchy(LineSegment ls, Polyline polylineToExclude0, Polyline polylineToExclude1) { bool lineIsGood = true; foreach (IntersectionInfo ii in IntersectionsOfLineAndRectangleNodeOverPolyline(ls, ObstacleCalculator.RootOfTightHierarchy)) if (!(ii.Segment1 == polylineToExclude0 || ii.Segment1 == polylineToExclude1)) { lineIsGood = false; break; } return lineIsGood; } bool LineAvoidsTightHierarchy(Point a, Point b, Polyline polylineToExclude0, Polyline polylineToExclude1) { return LineAvoidsTightHierarchy(new LineSegment(a, b), polylineToExclude0, polylineToExclude1); } void ExtendVisibilityGraphToTargetBoundaryPort(Point takenOutTargetPortLocation) { List addedPolygons = null; if (VisibilityGraph == null) VisibilityGraph = new VisibilityGraph(); if (!activeRectangle.Contains(takenOutTargetPortLocation) || !activeRectangle.Contains(TargetLoosePolyline.BoundingBox)) { if (activeRectangle.IsEmpty) { #if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=369 there are no structs in js activeRectangle = TargetLoosePolyline.BoundingBox.Clone(); #else activeRectangle = TargetLoosePolyline.BoundingBox; #endif activeRectangle.Add(SourcePort.Location); activeRectangle.Add(StartPointOfEdgeRouting); activeRectangle.Add(takenOutTargetPortLocation); } else { activeRectangle.Add(takenOutTargetPortLocation); activeRectangle.Add(TargetLoosePolyline.BoundingBox); } addedPolygons = GetAddedPolygonesAndMaybeExtendActiveRectangle(); foreach (Polygon polygon in addedPolygons) VisibilityGraph.AddHole(polygon.Polyline); } if (addedPolygons == null) { if (targetVisibilityVertex != null) VisibilityGraph.RemoveVertex(targetVisibilityVertex); CalculateEdgeTargetVisibilityGraph(takenOutTargetPortLocation); } else { RemovePointVisibilityGraphs(); var visibilityGraphGenerator = new InteractiveTangentVisibilityGraphCalculator(addedPolygons, activePolygons, VisibilityGraph); visibilityGraphGenerator.Run(); activePolygons.AddRange(addedPolygons); CalculateEdgeTargetVisibilityGraph(takenOutTargetPortLocation); CalculateSourcePortVisibilityGraph(); } } /// /// returns the hit object /// /// /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Polyline")] public Polyline GetHitLoosePolyline(Point point) { if (ObstacleCalculator.IsEmpty() || ObstacleCalculator.RootOfLooseHierarchy == null) return null; return GetFirstHitPolyline(point, ObstacleCalculator.RootOfLooseHierarchy); } internal static Polyline GetFirstHitPolyline(Point point, RectangleNode rectangleNode) { RectangleNode rectNode = GetFirstHitRectangleNode(point, rectangleNode); return rectNode != null ? rectNode.UserData : null; } static RectangleNode GetFirstHitRectangleNode(Point point, RectangleNode rectangleNode) { if (rectangleNode == null) return null; return rectangleNode.FirstHitNode(point, (pnt, polyline) => Curve.PointRelativeToCurveLocation(pnt, polyline) != PointLocation.Outside ? HitTestBehavior.Stop : HitTestBehavior.Continue); } /// /// /// public void Clean() { SourcePort = TargetPort = null; SourceLoosePolyline = SourceTightPolyline = null; targetTightPolyline = TargetLoosePolyline = null; VisibilityGraph = null; _sourceVisibilityVertex = targetVisibilityVertex = null; activePolygons.Clear(); alreadyAddedOrExcludedPolylines.Clear(); activeRectangle.SetToEmpty(); } /// /// setting source port and the loose polyline of the port /// /// /// [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "polyline"), SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Polyline")] public void SetSourcePortAndSourceLoosePolyline(Port port, Polyline sourceLoosePolylinePar) { SourceLoosePolyline = sourceLoosePolylinePar; sourcePort = port; if (sourcePort != null) { SourceTightPolyline = GetFirstHitPolyline(sourcePort.Location, ObstacleCalculator.RootOfTightHierarchy); if (sourcePort is FloatingPort) { alreadyAddedOrExcludedPolylines.Insert(SourceLoosePolyline); //we need to exclude the loose polyline around the source port from the tangent visibily graph StartPointOfEdgeRouting = SourcePort.Location; } else StartPointOfEdgeRouting = TakeBoundaryPortOutsideOfItsLoosePolyline(SourcePort.Curve, ((CurvePort) sourcePort). Parameter, SourceLoosePolyline); } } /// /// /// protected override void RunInternal() { CalculateWholeTangentVisibilityGraph(); } internal void CalculateWholeTangentVisibilityGraph() { VisibilityGraph = new VisibilityGraph(); CalculateWholeVisibilityGraphOnExistingGraph(); } internal void CalculateWholeVisibilityGraphOnExistingGraph() { activePolygons = new List(AllPolygons()); foreach (Polyline polylineLocal in ObstacleCalculator.LooseObstacles) VisibilityGraph.AddHole(polylineLocal); AlgorithmBase visibilityGraphGenerator; if (UseSpanner) { visibilityGraphGenerator = new ConeSpanner(ObstacleCalculator.LooseObstacles, VisibilityGraph) {ConeAngle = ConeSpannerAngle}; } else { visibilityGraphGenerator = new InteractiveTangentVisibilityGraphCalculator(new List(), activePolygons, visibilityGraph); } visibilityGraphGenerator.Run(); } /// /// /// /// /// /// /// [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")] public ICurve RouteSplineFromPortToPortWhenTheWholeGraphIsReady(Port sourcePortLocal, Port targetPortLocal, bool smooth, out SmoothedPolyline smoothedPolyline) { bool reversed = (sourcePortLocal is FloatingPort && targetPortLocal is CurvePort) || sourcePortLocal is HookUpAnywhereFromInsidePort; if (reversed) { Port tmp = sourcePortLocal; sourcePortLocal = targetPortLocal; targetPortLocal = tmp; } sourcePort = sourcePortLocal; targetPort = targetPortLocal; FigureOutSourceTargetPolylinesAndActiveRectangle(); ICurve curve = GetEdgeGeomByRouting(smooth, out smoothedPolyline); if (curve == null) return null; _sourceVisibilityVertex = targetVisibilityVertex = null; if (reversed) curve = curve.Reverse(); return curve; } ICurve GetEdgeGeomByRouting(bool smooth, out SmoothedPolyline smoothedPolyline) { targetIsInsideOfSourceTightPolyline = SourceTightPolyline == null || Curve.PointRelativeToCurveLocation(targetPort.Location, SourceTightPolyline) == PointLocation.Inside; sourceIsInsideOfTargetTightPolyline = TargetTightPolyline == null || Curve.PointRelativeToCurveLocation(sourcePort.Location, TargetTightPolyline) == PointLocation.Inside; var curvePort = sourcePort as CurvePort; ICurve curve; if (curvePort != null) { StartPointOfEdgeRouting = !targetIsInsideOfSourceTightPolyline ? TakeBoundaryPortOutsideOfItsLoosePolyline(curvePort.Curve, curvePort.Parameter, SourceLoosePolyline) : curvePort.Location; CalculateSourcePortVisibilityGraph(); if (targetPort is CurvePort) curve = RouteFromBoundaryPortToBoundaryPort(smooth, out smoothedPolyline); else curve = RouteFromBoundaryPortToFloatingPort(targetLoosePolyline, smooth, out smoothedPolyline); } else { if (targetPort is FloatingPort) { ExtendVisibilityGraphFromFloatingSourcePort(); Debug.Assert(_sourceVisibilityVertex != null); //the edge has to be reversed to route from CurvePort to FloatingPort curve = RouteFromFloatingPortToFloatingPort(targetLoosePolyline, smooth, out smoothedPolyline); } else curve = RouteFromFloatingPortToAnywherePort(((HookUpAnywhereFromInsidePort) targetPort).LoosePolyline, smooth, out smoothedPolyline, (HookUpAnywhereFromInsidePort) targetPort); } return curve; } ICurve RouteFromFloatingPortToAnywherePort(Polyline targetLoosePoly, bool smooth, out SmoothedPolyline smoothedPolyline, HookUpAnywhereFromInsidePort port) { if (!port.Curve.BoundingBox.Contains(sourcePort.Location)) { smoothedPolyline = null; return null; } _sourceVisibilityVertex = GetVertex(sourcePort.Location); _polyline = GetShortestPolylineToMulitpleTargets(SourceVisibilityVertex, Targets(targetLoosePoly)); if (_polyline == null) { smoothedPolyline = null; return null; } if (UseSpanner) TryShortcutPolyline(); RelaxPolyline(); FixLastPolylinePointForAnywherePort(port); if (port.HookSize > 0) BuildHook(port); return SmoothCornersAndReturnCurve(smooth, out smoothedPolyline); } void BuildHook(HookUpAnywhereFromInsidePort port) { var curve = port.Curve; //creating a hook var ellipse = new Ellipse(port.HookSize, port.HookSize, _polyline.End); var intersections = Curve.GetAllIntersections(curve, ellipse, true).ToArray(); Debug.Assert(intersections.Length == 2); if (Point.GetTriangleOrientation(intersections[0].IntersectionPoint, _polyline.End, _polyline.EndPoint.Prev.Point) == TriangleOrientation.Counterclockwise) intersections.Reverse(); //so the [0] point is to the left of the Polyline var polylineTangent = (_polyline.End - _polyline.EndPoint.Prev.Point).Normalize(); var tan0 = curve.Derivative(intersections[0].Par0).Normalize(); var prj0 = tan0 * polylineTangent; if (Math.Abs(prj0) < 0.2) ExtendPolyline(tan0, intersections[0], polylineTangent, port); else { var tan1 = curve.Derivative(intersections[1].Par0).Normalize(); var prj1 = tan1 * polylineTangent; if (prj1 < prj0) ExtendPolyline(tan1, intersections[1], polylineTangent, port); else ExtendPolyline(tan0, intersections[0], polylineTangent, port); } } void ExtendPolyline(Point tangentAtIntersection, IntersectionInfo x, Point polylineTangent, HookUpAnywhereFromInsidePort port) { var normal=tangentAtIntersection.Rotate(Math.PI/2); if(normal*polylineTangent<0) normal=-normal; var pointBeforeLast = x.IntersectionPoint + normal * port.HookSize; Point pointAfterX; if (!Point.LineLineIntersection(pointBeforeLast, pointBeforeLast+tangentAtIntersection, _polyline.End, _polyline.End+polylineTangent, out pointAfterX)) return; _polyline.AddPoint(pointAfterX); _polyline.AddPoint(pointBeforeLast); _polyline.AddPoint(x.IntersectionPoint); } void FixLastPolylinePointForAnywherePort(HookUpAnywhereFromInsidePort port) { while (true) { PolylinePoint lastPointInside = GetLastPointInsideOfCurveOnPolyline(port.Curve); lastPointInside.Next.Next = null; _polyline.EndPoint=lastPointInside.Next; var dir = lastPointInside.Next.Point - lastPointInside.Point; dir = dir.Normalize()*port.Curve.BoundingBox.Diagonal; //make it a long vector var dir0 = dir.Rotate(-port.AdjustmentAngle); var dir1 = dir.Rotate(port.AdjustmentAngle); var rx=Curve.CurveCurveIntersectionOne(port.Curve, new LineSegment(lastPointInside.Point, lastPointInside.Point+dir0), true); var lx=Curve.CurveCurveIntersectionOne(port.Curve, new LineSegment(lastPointInside.Point, lastPointInside.Point+dir1), true); if (rx == null || lx == null) return; //this.ShowPolylineAndObstacles(Polyline, new LineSegment(lastPointInside.Point, lastPointInside.Point+dir0), new LineSegment(lastPointInside.Point, rerPoint+dir1), port.Curve); var trimmedCurve = GetTrimmedCurveForHookingUpAnywhere(port.Curve, lastPointInside, rx, lx); var newLastPoint = trimmedCurve[trimmedCurve.ClosestParameter(lastPointInside.Point)]; if (!LineAvoidsTightHierarchy(new LineSegment(lastPointInside.Point, newLastPoint), SourceTightPolyline, null)) { var xx=Curve.CurveCurveIntersectionOne(port.Curve, new LineSegment(lastPointInside.Point, lastPointInside.Next.Point), false); if (xx == null) return; //this.ShowPolylineAndObstacles(Polyline, port.Curve); _polyline.EndPoint.Point = xx.IntersectionPoint; break; } _polyline.EndPoint.Point = newLastPoint; if (lastPointInside.Prev == null || !TryShortcutPolyPoint(lastPointInside.Prev)) break; } } static ICurve GetTrimmedCurveForHookingUpAnywhere(ICurve curve, PolylinePoint lastPointInside, IntersectionInfo x0, IntersectionInfo x1) { var clockwise = Point.GetTriangleOrientation(x1.IntersectionPoint, x0.IntersectionPoint, lastPointInside.Point) == TriangleOrientation.Clockwise; double rightX = x0.Par0; double leftX = x1.Par0; ICurve tr0, tr1; Curve ret; if (clockwise) { if (rightX < leftX) return curve.Trim(rightX, leftX); tr0 = curve.Trim(rightX, curve.ParEnd); tr1 = curve.Trim(curve.ParStart, leftX); ret = new Curve(); return ret.AddSegs(tr0, tr1); } if (leftX < rightX) return curve.Trim(leftX, rightX); tr0 = curve.Trim(leftX, curve.ParEnd); tr1 = curve.Trim(curve.ParStart, rightX); ret = new Curve(); return ret.AddSegs(tr0, tr1); } PolylinePoint GetLastPointInsideOfCurveOnPolyline(ICurve curve) { for (var p = _polyline.EndPoint.Prev; p != null; p = p.Prev) { if (p.Prev == null) return p; if (Curve.PointRelativeToCurveLocation(p.Point, curve) == PointLocation.Inside) return p; } throw new InvalidOperationException(); } Polyline GetShortestPolylineToMulitpleTargets(VisibilityVertex sourceVisVertex, IEnumerable targets) { CleanTheGraphForShortestPath(); //ShowPolylineAndObstacles(targets.Select(t=>new Ellipse(3,3,t.Point)).ToArray()); var pathCalc = new SingleSourceMultipleTargetsShortestPathOnVisibilityGraph(sourceVisVertex, targets, VisibilityGraph);// { dd = ShowPolylineAndObstacles }; IEnumerable path = pathCalc.GetPath(); if (path == null) return null; Debug.Assert(path.First() == sourceVisVertex && targets.Contains(path.Last())); var ret = new Polyline(); foreach (VisibilityVertex v in path) ret.AddPoint(v.Point); return RemoveCollinearVertices(ret); } IEnumerable Targets(Polyline targetLoosePoly) { return new List (targetLoosePoly.Select(p=>visibilityGraph.FindVertex(p))); } void ExtendVisibilityGraphFromFloatingSourcePort() { var fp = sourcePort as FloatingPort; Debug.Assert(fp != null); StartPointOfEdgeRouting = fp.Location; if (UseSpanner) _sourceVisibilityVertex = AddTransientVisibilityEdgesForPort(sourcePort.Location, SourceLoosePolyline); else { PointVisibilityCalculator.CalculatePointVisibilityGraph( from p in GetActivePolylines() where p != SourceLoosePolyline select p, VisibilityGraph, StartPointOfEdgeRouting, VisibilityKind.Tangent, out _sourceVisibilityVertex); } } void FigureOutSourceTargetPolylinesAndActiveRectangle() { _sourceTightPolyline = GetFirstHitPolyline(sourcePort.Location, ObstacleCalculator.RootOfTightHierarchy); SourceLoosePolyline = GetFirstHitPolyline(sourcePort.Location, ObstacleCalculator.RootOfLooseHierarchy); targetTightPolyline = GetFirstHitPolyline(targetPort.Location, ObstacleCalculator.RootOfTightHierarchy); targetLoosePolyline = GetFirstHitPolyline(targetPort.Location, ObstacleCalculator.RootOfLooseHierarchy); activeRectangle = new Rectangle(new Point(double.NegativeInfinity, double.PositiveInfinity), new Point(double.PositiveInfinity, double.NegativeInfinity)); } IEnumerable AllPolygons() { foreach (Polyline p in ObstacleCalculator.LooseObstacles) yield return new Polygon(p); } /// /// /// [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")] public VisibilityGraph GetVisibilityGraph() { return VisibilityGraph; } // internal void CalculateVisibilityGraph(IEnumerable edgeGeometries, bool qualityAtPorts) // { // CalculateWholeTangentVisibilityGraph(); // if (ConeSpannerAngle > 0 && qualityAtPorts && edgeGeometries != null) // CalculatePortVisibilityGraph(GetPortLocationsPointSet(edgeGeometries)); // } #if DEBUG_MSAGL && !SILVERLIGHT internal void ShowObstaclesAndVisGraph() { var obs = this.obstacleCalculator.LooseObstacles.Select(o => new DebugCurve(100, 1, "blue", o)); var edges = visibilityGraph.Edges.Select( e => new DebugCurve(70, 1, (e is TransientVisibilityEdge ? "red" : "green"), new LineSegment(e.SourcePoint, e.TargetPoint))); LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(obs.Concat(edges)); } #endif internal void AddActivePolygons(IEnumerable polygons) { activePolygons.AddRange(polygons); } internal void ClearActivePolygons() { activePolygons.Clear(); } } }