using System; using System.Collections.Generic; using System.Diagnostics; 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.Core.Routing; using Microsoft.Msagl.Layout.LargeGraphLayout; using Microsoft.Msagl.Routing.ConstrainedDelaunayTriangulation; using Microsoft.Msagl.Routing.Spline.Bundling; using Microsoft.Msagl.Routing.Spline.ConeSpanner; using Microsoft.Msagl.Routing.Visibility; #if DEBUG && TEST_MSAGL using Microsoft.Msagl.DebugHelpers; using System.Diagnostics.CodeAnalysis; #endif namespace Microsoft.Msagl.Routing { /// /// routing splines around shapes /// public class SplineRouter : AlgorithmBase { /// /// setting this to true forces the calculation to go on even when node overlaps are present /// /// bool continueOnOverlaps = true; public bool ContinueOnOverlaps { get { return continueOnOverlaps; } set { continueOnOverlaps = value; } } Shape[] rootShapes; IEnumerable edgeGeometriesEnumeration { get { if (this._edges != null) { foreach (var item in this._edges.Select(e => e.EdgeGeometry)) { yield return item; } } } } double coneAngle; readonly double tightPadding; double LoosePadding { get; set; } bool rootWasCreated; Shape root; VisibilityGraph visGraph; Dictionary> ancestorSets; readonly Dictionary shapesToTightLooseCouples = new Dictionary(); Dictionary portsToShapes; Dictionary> portsToEnterableShapes; RTree portRTree; readonly Dictionary portLocationsToLoosePolylines = new Dictionary(); Shape looseRoot; internal BundlingSettings BundlingSettings { get; set; } Dictionary> enterableLoose; Dictionary> enterableTight; readonly GeometryGraph geometryGraph; double multiEdgesSeparation=5; bool routeMultiEdgesAsBundles = true; internal bool UseEdgeLengthMultiplier; /// /// if set to true the algorithm will try to shortcut a shortest polyline inner points /// public bool UsePolylineEndShortcutting = true; /// /// if set to true the algorithm will try to shortcut a shortest polyline start and end /// public bool UseInnerPolylingShortcutting = true; internal bool AllowedShootingStraightLines = true; internal double MultiEdgesSeparation { get { return multiEdgesSeparation; } set { multiEdgesSeparation = value; } } /// /// Creates a spline group router for the given graph. /// public SplineRouter(GeometryGraph graph, EdgeRoutingSettings edgeRoutingSettings) : this( graph, edgeRoutingSettings.Padding, edgeRoutingSettings.PolylinePadding, edgeRoutingSettings.ConeAngle, edgeRoutingSettings.BundlingSettings) {} /// /// Creates a spline group router for the given graph. /// public SplineRouter(GeometryGraph graph, double tightTightPadding, double loosePadding, double coneAngle) : this(graph, graph.Edges, tightTightPadding, loosePadding, coneAngle, null) { } /// /// Creates a spline group router for the given graph /// public SplineRouter(GeometryGraph graph, double tightTightPadding, double loosePadding, double coneAngle, BundlingSettings bundlingSettings) : this(graph, graph.Edges, tightTightPadding, loosePadding, coneAngle, bundlingSettings) { } /// /// Creates a spline group router for the given graph. /// public SplineRouter(GeometryGraph graph, IEnumerable edges, double tightPadding, double loosePadding, double coneAngle, BundlingSettings bundlingSettings) { ValidateArg.IsNotNull(graph, "graph"); ValidateArg.IsPositive(tightPadding, "tightPadding"); ValidateArg.IsPositive(loosePadding, "loosePadding"); ValidateArg.IsNotNull(edges, "edges"); #if TEST_MSAGL // do not run the following check, cluster containment may be incorrect in float mode - need to make sure we handle this //graph.CheckClusterConsistency(); #endif this._edges = edges.ToArray(); BundlingSettings=bundlingSettings; geometryGraph = graph; LoosePadding = loosePadding; this.tightPadding = tightPadding; IEnumerable obstacles = ShapeCreator.GetShapes(geometryGraph); Initialize(obstacles, coneAngle); } readonly Edge[] _edges; internal Action ReplaceEdgeByRails; /// /// /// /// /// /// /// /// /// public SplineRouter(GeometryGraph graph, double tightPadding, double loosePadding, double coneAngle, List inParentEdges, List outParentEdges) { // #if TEST_MSAGL // graph.CheckClusterConsistency(); // #endif geometryGraph = graph; LoosePadding = loosePadding; this.tightPadding = tightPadding; IEnumerable obstacles = ShapeCreatorForRoutingToParents.GetShapes(inParentEdges, outParentEdges); Initialize(obstacles, coneAngle); } void Initialize(IEnumerable obstacles, double coneAngleValue) { rootShapes = obstacles.Where(s => s.Parents == null || !s.Parents.Any()).ToArray(); coneAngle = coneAngleValue; if (coneAngle == 0) coneAngle = Math.PI / 6; } /// /// Executes the algorithm. /// protected override void RunInternal() { if (!edgeGeometriesEnumeration.Any()) return; GetOrCreateRoot(); RouteOnRoot(); RemoveRoot(); /* var ll = new List(); ll.AddRange(rootShapes.Select(s=>shapesToTightLooseCouples[s].TightPolyline).Select(p=>new DebugCurve(100,0.05,"black", p))); ll.AddRange(geometryGraph.Edges.Select(s => new DebugCurve(100, 0.05, "black", s.Curve))); LayoutAlgorithmSettings.ShowDebugCurvesEnumeration (ll); LayoutAlgorithmSettings.ShowGraph(geometryGraph);*/ } void RouteOnRoot() { CalculatePortsToShapes(); CalculatePortsToEnterableShapes(); CalculateShapeToBoundaries(root); if (OverlapsDetected && !ContinueOnOverlaps) return; BindLooseShapes(); SetLoosePolylinesForAnywherePorts(); CalculateVisibilityGraph(); RouteOnVisGraph(); } /* IEnumerable AllPolysDeb() { foreach (var rootShape in looseRoot.Children) { foreach (var poly in PolysDebugUnderShape(rootShape)) { yield return poly; } } } IEnumerable PolysDebugUnderShape(Shape shape) { yield return (Polyline)shape.BoundaryCurve; foreach (var child in shape.Children) { foreach (var poly in PolysDebugUnderShape(child)) { yield return poly; } } } */ void CalculatePortsToEnterableShapes() { portsToEnterableShapes = new Dictionary>(); foreach (var portsToShape in portsToShapes) { var port = portsToShape.Key; var set = new Set(); if (!(EdgesAttachedToPortAvoidTheNode(port))) set.Insert(portsToShape.Value); portsToEnterableShapes[port] = set; } foreach (var rootShape in rootShapes) { foreach(var sh in rootShape.Descendants) foreach (var port in sh.Ports) { var enterableSet = portsToEnterableShapes[port]; enterableSet.InsertRange(sh.Ancestors.Where(s => s.BoundaryCurve != null)); } } } static bool EdgesAttachedToPortAvoidTheNode(Port port) { return port is CurvePort || port is ClusterBoundaryPort; } void SetLoosePolylinesForAnywherePorts() { foreach (var shapesToTightLooseCouple in shapesToTightLooseCouples) { var shape = shapesToTightLooseCouple.Key; foreach (var port in shape.Ports) { var aport = port as HookUpAnywhereFromInsidePort; if (aport != null) aport.LoosePolyline = (Polyline)shapesToTightLooseCouple.Value.LooseShape.BoundaryCurve; var clusterBoundaryPort = port as ClusterBoundaryPort; if (clusterBoundaryPort != null) clusterBoundaryPort.LoosePolyline = (Polyline)shapesToTightLooseCouple.Value.LooseShape.BoundaryCurve; } } } void BindLooseShapes() { looseRoot = new Shape(); #if TEST_MSAGL looseRoot.UserData = (string)root.UserData + "x"; #endif foreach (var shape in root.Children) { var looseShape = shapesToTightLooseCouples[shape].LooseShape; BindLooseShapesUnderShape(shape); looseRoot.AddChild(looseShape); } } void BindLooseShapesUnderShape(Shape shape) { var loose = shapesToTightLooseCouples[shape].LooseShape; foreach (var child in shape.Children) { var childLooseShape = shapesToTightLooseCouples[child].LooseShape; loose.AddChild(childLooseShape); BindLooseShapesUnderShape(child); } } void CalculateShapeToBoundaries(Shape shape) { ProgressStep(); if (!shape.Children.Any()) return; foreach (Shape child in shape.Children) CalculateShapeToBoundaries(child); var obstacleCalculator = new ShapeObstacleCalculator(shape, tightPadding, AdjustedLoosePadding, shapesToTightLooseCouples); obstacleCalculator.Calculate(); #if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=370 //SharpKit/Colin - not supported directly! OverlapsDetected = OverlapsDetected | obstacleCalculator.OverlapsDetected; #else OverlapsDetected |= obstacleCalculator.OverlapsDetected; #endif } /// /// set to true if and only if there are overlaps in tight obstacles /// public bool OverlapsDetected { get; set; } internal double AdjustedLoosePadding { get { return BundlingSettings == null ? LoosePadding : LoosePadding * BundleRouter.SuperLoosePaddingCoefficient; } } void RouteOnVisGraph() { ancestorSets = GetAncestorSetsMap(root.Descendants); if (BundlingSettings == null) { foreach (var edgeGroup in _edges.GroupBy(EdgePassport)) { var passport = edgeGroup.Key; Set obstacleShapes = GetObstaclesFromPassport(passport); var interactiveEdgeRouter = CreateInteractiveEdgeRouter(obstacleShapes); RouteEdgesWithTheSamePassport(edgeGroup, interactiveEdgeRouter, obstacleShapes); } } else RouteBundles(); } void RouteEdgesWithTheSamePassport(IGrouping, Edge> edgeGeometryGroup, InteractiveEdgeRouter interactiveEdgeRouter, Set obstacleShapes) { List regularEdges; List multiEdges; if (RouteMultiEdgesAsBundles) { SplitOnRegularAndMultiedges(edgeGeometryGroup, out regularEdges, out multiEdges); foreach (var edge in regularEdges) RouteEdge(interactiveEdgeRouter, edge); if (multiEdges != null) { ScaleDownLooseHierarchy(interactiveEdgeRouter, obstacleShapes); RouteMultiEdges(multiEdges, interactiveEdgeRouter, edgeGeometryGroup.Key); } } else foreach (var eg in edgeGeometryGroup) RouteEdge(interactiveEdgeRouter, eg); } /// /// if set to true routes multi edges as ordered bundles /// public bool RouteMultiEdgesAsBundles { get { return routeMultiEdgesAsBundles; } set { routeMultiEdgesAsBundles = value; } } void RouteEdge(InteractiveEdgeRouter interactiveEdgeRouter, Edge edge) { var transparentShapes = MakeTransparentShapesOfEdgeGeometryAndGetTheShapes(edge.EdgeGeometry); ProgressStep(); RouteEdgeGeometry(edge, interactiveEdgeRouter); SetTransparency(transparentShapes, false); } void ScaleDownLooseHierarchy(InteractiveEdgeRouter interactiveEdgeRouter, Set obstacleShapes) { var loosePolys = new List(); foreach (var obstacleShape in obstacleShapes) { var tl = shapesToTightLooseCouples[obstacleShape]; loosePolys.Add( InteractiveObstacleCalculator.LoosePolylineWithFewCorners(tl.TightPolyline, tl.Distance / BundleRouter.SuperLoosePaddingCoefficient)); } interactiveEdgeRouter.LooseHierarchy=CreateLooseObstacleHierarachy(loosePolys); interactiveEdgeRouter.ClearActivePolygons(); interactiveEdgeRouter.AddActivePolygons(loosePolys.Select(polyline => new Polygon(polyline))); } void RouteMultiEdges(List multiEdges, InteractiveEdgeRouter interactiveEdgeRouter, Set parents) { var mer = new MultiEdgeRouter(multiEdges, interactiveEdgeRouter, parents.SelectMany(p => p.Children).Select(s => s.BoundaryCurve), new BundlingSettings { InkImportance = 0.00001, EdgeSeparation = MultiEdgesSeparation}, MakeTransparentShapesOfEdgeGeometryAndGetTheShapes); //giving more importance to ink might produce weird routings with huge detours, maybe 0 is the best value here mer.Run(); } // void ScaleLoosePolylinesOfInvolvedShapesDown(Set parents) { // foreach (var parent in parents) { // foreach (var shape in parent.Descendands) { // TightLooseCouple tl = this.shapesToTightLooseCouples[shape]; // tl.LooseShape.BoundaryCurveBackup = tl.LooseShape.BoundaryCurve; // tl.LooseShape.BoundaryCurve = InteractiveObstacleCalculator.LoosePolylineWithFewCorners(tl.TightPolyline, tl.Distance / BundleRouter.SuperLoosePaddingCoefficient); // } // } // } // // void RestoreLoosePolylinesOfInvolvedShapes(Set parents) { // foreach (var parent in parents) { // foreach (var shape in parent.Descendands) { // TightLooseCouple tl = shapesToTightLooseCouples[shape]; // tl.LooseShape.BoundaryCurve = tl.LooseShape.BoundaryCurveBackup; // } // } // } void SplitOnRegularAndMultiedges(IEnumerable edges, out List regularEdges, out List multiEdges) { regularEdges = new List(); var portLocationPairsToEdges = new Dictionary>(); foreach (var eg in edges) { if (IsEdgeToParent(eg.EdgeGeometry)) regularEdges.Add(eg); else RegisterInPortLocationsToEdges(eg, portLocationPairsToEdges); } multiEdges = null; foreach (var edgeGroup in portLocationPairsToEdges.Values) { if (edgeGroup.Count == 1 || OverlapsDetected) regularEdges.AddRange(edgeGroup); else { if (multiEdges == null) multiEdges = new List(); multiEdges.Add(edgeGroup.ToArray()); } } } static void RegisterInPortLocationsToEdges(Edge eg, Dictionary> portLocationPairsToEdges) { List list; var pp = new PointPair(eg.SourcePort.Location, eg.TargetPort.Location); if (!portLocationPairsToEdges.TryGetValue(pp, out list)) portLocationPairsToEdges[pp] = list = new List(); list.Add(eg); } static bool IsEdgeToParent(EdgeGeometry e) { return e.SourcePort is HookUpAnywhereFromInsidePort || e.TargetPort is HookUpAnywhereFromInsidePort; } InteractiveEdgeRouter CreateInteractiveEdgeRouter(IEnumerable obstacleShapes) { //we need to create a set here because one loose polyline can hold several original shapes var loosePolys = new Set(obstacleShapes.Select(sh => shapesToTightLooseCouples[sh].LooseShape.BoundaryCurve as Polyline)); var router = new InteractiveEdgeRouter { VisibilityGraph = visGraph, TightHierarchy = CreateTightObstacleHierarachy(obstacleShapes), LooseHierarchy = CreateLooseObstacleHierarachy(loosePolys), UseSpanner = true, LookForRoundedVertices = true, TightPadding = tightPadding, LoosePadding = LoosePadding, UseEdgeLengthMultiplier = UseEdgeLengthMultiplier, UsePolylineEndShortcutting = UsePolylineEndShortcutting, UseInnerPolylingShortcutting = UseInnerPolylingShortcutting, AllowedShootingStraightLines = AllowedShootingStraightLines, CacheCorners = CacheCornersForSmoothing, }; router.AddActivePolygons(loosePolys.Select(polyline => new Polygon(polyline))); return router; } /// /// /// public bool CacheCornersForSmoothing { get; set; } Set GetObstaclesFromPassport(Set passport) { if (passport.Count == 0) return new Set(root.Children); var commonAncestors = GetCommonAncestorsAbovePassport(passport); var allAncestors = GetAllAncestors(passport); var ret = new Set(passport.SelectMany(p => p.Children.Where(child => !allAncestors.Contains(child)))); var enqueued = new Set(passport.Concat(ret)); var queue = new Queue(); foreach (var shape in passport.Where(shape => !commonAncestors.Contains(shape))) queue.Enqueue(shape); while (queue.Count > 0) { var a = queue.Dequeue(); foreach (var parent in a.Parents) { foreach (var sibling in parent.Children) if (!allAncestors.Contains(sibling)) ret.Insert(sibling); if (!commonAncestors.Contains(parent) && !enqueued.Contains(parent)) { queue.Enqueue(parent); enqueued.Insert(parent); } } } return ret; } Set GetAllAncestors(Set passport) { if (!passport.Any()) return new Set(); var ret = new Set(passport); foreach (var shape in passport) ret += ancestorSets[shape]; return ret; } Set GetCommonAncestorsAbovePassport(Set passport) { if (!passport.Any()) return new Set(); var ret = ancestorSets[passport.First()]; foreach (var shape in passport.Skip(1)) ret *= ancestorSets[shape]; return ret; } void RouteBundles() { ScaleLooseShapesDown(); CalculateEdgeEnterablePolylines(); var looseHierarchy = GetLooseHierarchy(); var cdt = BundleRouter.CreateConstrainedDelaunayTriangulation(looseHierarchy); // CdtSweeper.ShowFront(cdt.GetTriangles(), null, null,this.visGraph.Edges.Select(e=>new LineSegment(e.SourcePoint,e.TargetPoint))); var shortestPath = new SdShortestPath(MakeTransparentShapesOfEdgeGeometryAndGetTheShapes, cdt, FindCdtGates(cdt)); var bundleRouter = new BundleRouter(geometryGraph, shortestPath, visGraph, BundlingSettings, LoosePadding, GetTightHierarchy(), looseHierarchy, enterableLoose, enterableTight, port=>LoosePolyOfOriginalShape(portsToShapes[port])); bundleRouter.Run(); } void CreateTheMapToParentLooseShapes(Shape shape, Dictionary loosePolylinesToLooseParentShapeMap) { foreach (var childShape in shape.Children) { var tightLooseCouple = shapesToTightLooseCouples[childShape]; var poly = tightLooseCouple.LooseShape.BoundaryCurve; loosePolylinesToLooseParentShapeMap[poly] = shape; CreateTheMapToParentLooseShapes(childShape, loosePolylinesToLooseParentShapeMap); } } Set FindCdtGates(Cdt cdt) { Dictionary loosePolylinesToLooseParentShapeMap = new Dictionary(); CreateTheMapToParentLooseShapes(root, loosePolylinesToLooseParentShapeMap); //looking for Cdt edges connecting two siblings; only those we define as gates var gates = new Set(); foreach (var cdtSite in cdt.PointsToSites.Values) { foreach (var cdtEdge in cdtSite.Edges) { if(cdtEdge.CwTriangle==null && cdtEdge.CcwTriangle==null) continue; var a = (Polyline)cdtSite.Owner; var b = (Polyline)cdtEdge.lowerSite.Owner; if (a == b) continue; Shape aParent; Shape bParent; if(loosePolylinesToLooseParentShapeMap.TryGetValue(a, out aParent) && loosePolylinesToLooseParentShapeMap.TryGetValue(b, out bParent) && aParent==bParent) gates.Insert(cdtEdge); } } //CdtSweeper.ShowFront(cdt.GetTriangles(), null, // gates.Select(g => new LineSegment(g.upperSite.Point, g.lowerSite.Point)), null); return gates; } void CalculateEdgeEnterablePolylines() { enterableLoose = new Dictionary>(); enterableTight = new Dictionary>(); foreach (var edgeGeometry in edgeGeometriesEnumeration) { Set looseSet; Set tightSet; GetEdgeEnterablePolylines(edgeGeometry, out looseSet, out tightSet); enterableLoose[edgeGeometry] = looseSet; enterableTight[edgeGeometry] = tightSet; } } void GetEdgeEnterablePolylines(EdgeGeometry edgeGeometry, out Set looseEnterable, out Set tightEnterable) { looseEnterable = new Set(); tightEnterable = new Set(); var sourceShape = portsToShapes[edgeGeometry.SourcePort]; var targetShape = portsToShapes[edgeGeometry.TargetPort]; if (sourceShape != root) { looseEnterable.InsertRange(ancestorSets[sourceShape].Select(LoosePolyOfOriginalShape).Where(p => p != null)); tightEnterable.InsertRange(ancestorSets[sourceShape].Select(TightPolyOfOriginalShape).Where(p => p != null)); } if (targetShape != root) { looseEnterable.InsertRange(ancestorSets[targetShape].Select(LoosePolyOfOriginalShape).Where(p => p != null)); tightEnterable.InsertRange(ancestorSets[targetShape].Select(TightPolyOfOriginalShape).Where(p => p != null)); } } RectangleNode GetTightHierarchy() { return RectangleNode.CreateRectangleNodeOnEnumeration(shapesToTightLooseCouples.Values. Select(tl => new RectangleNode(tl.TightPolyline, tl.TightPolyline.BoundingBox))); } RectangleNode GetLooseHierarchy() { var loosePolylines = new Set(shapesToTightLooseCouples.Values.Select(tl => (Polyline)(tl.LooseShape.BoundaryCurve))); return RectangleNode.CreateRectangleNodeOnEnumeration(loosePolylines.Select(p => new RectangleNode(p, p.BoundingBox))); } void ScaleLooseShapesDown() { foreach (var shapesToTightLooseCouple in shapesToTightLooseCouples) { var tl = shapesToTightLooseCouple.Value; tl.LooseShape.BoundaryCurve = InteractiveObstacleCalculator.LoosePolylineWithFewCorners(tl.TightPolyline, tl.Distance / BundleRouter.SuperLoosePaddingCoefficient); } } /// /// The set of shapes where the edgeGeometry source and target ports shapes are citizens. /// In the simple case it is the union of the target port shape parents and the sourceport shape parents. /// When one end shape contains another, the passport is the set consisting of the end shape and all other shape parents. /// /// /// Set EdgePassport(Edge edge) { EdgeGeometry edgeGeometry = edge.EdgeGeometry; var ret = new Set(); var sourceShape = portsToShapes[edgeGeometry.SourcePort]; var targetShape = portsToShapes[edgeGeometry.TargetPort]; if (IsAncestor(sourceShape, targetShape)) { ret.InsertRange(targetShape.Parents); ret.Insert(sourceShape); return ret; } if (IsAncestor(targetShape, sourceShape)) { ret.InsertRange(sourceShape.Parents); ret.Insert(targetShape); return ret; } if (sourceShape != looseRoot) ret.InsertRange(sourceShape.Parents); if (targetShape != looseRoot) ret.InsertRange(targetShape.Parents); return ret; } IEnumerable AllPorts() { foreach (var edgeGeometry in edgeGeometriesEnumeration) { yield return edgeGeometry.SourcePort; yield return edgeGeometry.TargetPort; } } void CalculatePortsToShapes() { portsToShapes = new Dictionary(); foreach (var shape in root.Descendants) foreach (var port in shape.Ports) portsToShapes[port] = shape; //assign all orphan ports to the root foreach (var port in AllPorts().Where(p => !portsToShapes.ContainsKey(p))) { root.Ports.Insert(port); portsToShapes[port] = root; } } void RouteEdgeGeometry(Edge edge, InteractiveEdgeRouter iRouter) { var edgeGeometry = edge.EdgeGeometry; var addedEdges = new List(); if (!(edgeGeometry.SourcePort is HookUpAnywhereFromInsidePort)) addedEdges.AddRange(AddVisibilityEdgesFromPort(edgeGeometry.SourcePort)); if (!(edgeGeometry.TargetPort is HookUpAnywhereFromInsidePort)) addedEdges.AddRange(AddVisibilityEdgesFromPort(edgeGeometry.TargetPort)); SmoothedPolyline smoothedPolyline; if (!ApproximateComparer.Close(edgeGeometry.SourcePort.Location, edgeGeometry.TargetPort.Location)) edgeGeometry.Curve = iRouter.RouteSplineFromPortToPortWhenTheWholeGraphIsReady( edgeGeometry.SourcePort, edgeGeometry.TargetPort, true, out smoothedPolyline); else { edgeGeometry.Curve = Edge.RouteSelfEdge(edgeGeometry.SourcePort.Curve, Math.Max(LoosePadding * 2, edgeGeometry.GetMaxArrowheadLength()), out smoothedPolyline); } edgeGeometry.SmoothedPolyline = smoothedPolyline; if (edgeGeometry.Curve == null) throw new NotImplementedException(); foreach (var visibilityEdge in addedEdges) VisibilityGraph.RemoveEdge(visibilityEdge); Arrowheads.TrimSplineAndCalculateArrowheads(edgeGeometry, edgeGeometry.SourcePort.Curve, edgeGeometry.TargetPort.Curve, edgeGeometry.Curve, false, KeepOriginalSpline); if (ReplaceEdgeByRails != null) ReplaceEdgeByRails(edge); // SetTransparency(transparentShapes, false); } /// /// if set to true the original spline is kept under the corresponding EdgeGeometry /// public bool KeepOriginalSpline {get; set; } /// /// /// public double ArrowHeadRatio { get; set; } internal Point[] LineSweeperPorts { get; set; } /// /// /// IEnumerable AddVisibilityEdgesFromPort(Port port) { Shape portShape; TightLooseCouple boundaryCouple; if (port is CurvePort || !portsToShapes.TryGetValue(port, out portShape) || !shapesToTightLooseCouples.TryGetValue(portShape, out boundaryCouple)) return new VisibilityEdge[] { }; var portLoosePoly = boundaryCouple.LooseShape; return (from point in portLoosePoly.BoundaryCurve as Polyline where visGraph.FindEdge(port.Location, point) == null select visGraph.AddEdge(port.Location, point)); } List MakeTransparentShapesOfEdgeGeometryAndGetTheShapes(EdgeGeometry edgeGeometry) { //it is OK here to repeat a shape in the returned list Shape sourceShape = portsToShapes[edgeGeometry.SourcePort]; Shape targetShape = portsToShapes[edgeGeometry.TargetPort]; var transparentLooseShapes = new List(); foreach (var shape in GetTransparentShapes(edgeGeometry.SourcePort, edgeGeometry.TargetPort, sourceShape, targetShape).ToArray()) if (shape != null) transparentLooseShapes.Add(LooseShapeOfOriginalShape(shape)); foreach (var shape in portsToEnterableShapes[edgeGeometry.SourcePort]) transparentLooseShapes.Add(LooseShapeOfOriginalShape(shape)); foreach (var shape in portsToEnterableShapes[edgeGeometry.TargetPort]) transparentLooseShapes.Add(LooseShapeOfOriginalShape(shape)); SetTransparency(transparentLooseShapes, true); return transparentLooseShapes; } Shape LooseShapeOfOriginalShape(Shape s) { if (s == root) return looseRoot; return shapesToTightLooseCouples[s].LooseShape; } Polyline LoosePolyOfOriginalShape(Shape s) { return (Polyline)(LooseShapeOfOriginalShape(s).BoundaryCurve); } Polyline TightPolyOfOriginalShape(Shape s) { if (s == root) return null; return shapesToTightLooseCouples[s].TightPolyline; } #region debugging // #if DEBUG && TEST_MSAGL // [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // static internal void AnotherShowMethod(VisibilityGraph visGraph, // Port sourcePort, Port targetPort, IEnumerable obstacleShapes, ICurve curve) { // var dd = new List( // visGraph.Edges.Select( // e => // new DebugCurve(100, 0.1, GetEdgeColor(e, sourcePort, targetPort), // new LineSegment(e.SourcePoint, e.TargetPoint)))); // if (obstacleShapes != null) // dd.AddRange( // obstacleShapes.Select(s => new DebugCurve(1, s.BoundaryCurve))); // if (sourcePort != null && targetPort != null) // dd.AddRange(new[] { // new DebugCurve(CurveFactory.CreateDiamond(3, 3, sourcePort.Location)), // new DebugCurve(CurveFactory.CreateEllipse(3, 3, targetPort.Location)), // }); // if (curve != null) // dd.Add(new DebugCurve(5, "purple", curve)); // // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(dd); // } // // // static string GetEdgeColor(VisibilityEdge e, Port sourcePort, Port targetPort) { // if (sourcePort == null || targetPort == null) // return "green"; // if (ApproximateComparer.Close(e.SourcePoint, sourcePort.Location) || // ApproximateComparer.Close(e.SourcePoint, targetPort.Location) // || ApproximateComparer.Close(e.TargetPoint, sourcePort.Location) || // ApproximateComparer.Close(e.TargetPoint, targetPort.Location)) // return "lightgreen"; // return e.IsPassable == null || e.IsPassable() ? "green" : "red"; // } // #endif #endregion IEnumerable GetTransparentShapes( Port sourcePort, Port targetPort, Shape sourceShape, Shape targetShape) { foreach (var s in ancestorSets[sourceShape]) yield return s; foreach (var s in ancestorSets[targetShape]) yield return s; var routingOutsideOfSourceBoundary = EdgesAttachedToPortAvoidTheNode(sourcePort); var routingOutsideOfTargetBoundary = EdgesAttachedToPortAvoidTheNode(targetPort); if (!routingOutsideOfSourceBoundary && !routingOutsideOfTargetBoundary) { yield return sourceShape; yield return targetShape; } else if (routingOutsideOfSourceBoundary) { if (IsAncestor(sourceShape, targetShape)) yield return sourceShape; } else { if (IsAncestor(targetShape, sourceShape)) yield return targetShape; } } static void SetTransparency(IEnumerable shapes, bool v) { foreach (Shape shape in shapes) shape.IsTransparent = v; } /* /// /// it is the set of parents with all children being obstacle candidates /// /// /// /// /// IEnumerable FindMinimalParents(Port port, Shape shape, Shape otherShape) { if (shape == null) yield return rootOfLooseShapes; else if (port is CurvePort) { if (IsLgAncestor(shape, otherShape)) yield return shape; else foreach (Shape parent in shape.Parents) yield return parent; } else if (PortIsInsideOfShape(port, shape) && shape.Children.Any()) yield return shape; else foreach (Shape parent in shape.Parents) yield return parent; } */ bool IsAncestor(Shape possibleAncestor, Shape possiblePredecessor) { Set ancestors; return possiblePredecessor != null && ancestorSets.TryGetValue(possiblePredecessor, out ancestors) && ancestors != null && ancestors.Contains(possibleAncestor); } /* static bool PortIsInsideOfShape(Port port, Shape shape) { if (shape == null) return false; return Curve.PointRelativeToCurveLocation(ApproximateComparer.Round(port.Location), shape.BoundaryCurve) == PointLocation.Inside; } */ static RectangleNode CreateLooseObstacleHierarachy(IEnumerable loosePolys) { return RectangleNode.CreateRectangleNodeOnEnumeration( loosePolys.Select( poly => new RectangleNode(poly, poly.BoundingBox))); } RectangleNode CreateTightObstacleHierarachy(IEnumerable obstacles) { var tightPolys = obstacles.Select(sh => shapesToTightLooseCouples[sh].TightPolyline); return RectangleNode.CreateRectangleNodeOnEnumeration( tightPolys.Select(tightPoly => new RectangleNode( tightPoly, tightPoly.BoundingBox))); } void CalculateVisibilityGraph() { var setOfPortLocations = LineSweeperPorts != null ? new Set(LineSweeperPorts) : new Set(); foreach (var edgeGeometry in edgeGeometriesEnumeration) { if (!(edgeGeometry.SourcePort is HookUpAnywhereFromInsidePort || edgeGeometry.SourcePort is ClusterBoundaryPort)) setOfPortLocations.Insert(edgeGeometry.SourcePort.Location); if (!(edgeGeometry.TargetPort is HookUpAnywhereFromInsidePort || edgeGeometry.TargetPort is ClusterBoundaryPort)) setOfPortLocations.Insert(edgeGeometry.TargetPort.Location); } portRTree = new RTree( setOfPortLocations.Select(p => new KeyValuePair(new Rectangle(p), p))); visGraph = new VisibilityGraph(); FillVisibilityGraphUnderShape(root); // ShowVisGraph(visGraph, new Set(shapesToTightLooseCouples.Values.Select(tl => (Polyline)(tl.LooseShape.BoundaryCurve))), // geometryGraph.Nodes.Select(n => n.BoundaryCurve).Concat(root.Descendands.Select(d => d.BoundaryCurve)), null); } /// /// this function might change the shape's loose polylines by inserting new points /// void FillVisibilityGraphUnderShape(Shape shape) { //going depth first var children = shape.Children; foreach (Shape child in children) FillVisibilityGraphUnderShape(child); TightLooseCouple tightLooseCouple; Polyline looseBoundary = shapesToTightLooseCouples.TryGetValue(shape, out tightLooseCouple) ? tightLooseCouple.LooseShape.BoundaryCurve as Polyline : null; Shape looseShape = tightLooseCouple != null ? tightLooseCouple.LooseShape : looseRoot; var obstacles = new Set(looseShape.Children.Select(c => c.BoundaryCurve as Polyline)); var portLocations = RemoveInsidePortsAndSplitBoundaryIfNeeded(looseBoundary); //this run will split the polyline enough to route later from the inner ports var tmpVisGraph = new VisibilityGraph(); var coneSpanner = new ConeSpanner(new Polyline[] {}, tmpVisGraph, coneAngle, portLocations, looseBoundary); coneSpanner.Run(); //now run the spanner again to create the correct visibility graph around the inner obstacles tmpVisGraph = new VisibilityGraph(); coneSpanner = new ConeSpanner(obstacles, tmpVisGraph, coneAngle, portLocations, looseBoundary) { Bidirectional = Bidirectional && obstacles.Count>0 }; coneSpanner.Run(); ProgressStep(); foreach (VisibilityEdge edge in tmpVisGraph.Edges) TryToCreateNewEdgeAndSetIsPassable(edge, looseShape); AddBoundaryEdgesToVisGraph(looseBoundary); // if (obstacles.Count > 0) // SplineRouter.ShowVisGraph(tmpVisGraph, obstacles, null, null); } /// /// If set to true then a smaller visibility graph is created. /// An edge is added to the visibility graph only if it is found at least twice: /// once sweeping with a direction d and the second time with -d /// internal bool Bidirectional { get; set; } // #if TEST_MSAGL // [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // static internal void ShowVisGraph(VisibilityGraph tmpVisGraph, IEnumerable obstacles, IEnumerable greenCurves, IEnumerable redCurves) { // var l = new List(tmpVisGraph.Edges.Select(e => new DebugCurve(100, 1, // e.IsPassable != null && e.IsPassable() ? "green" : "black" // , new LineSegment(e.SourcePoint, e.TargetPoint)))); // if (obstacles != null) // l.AddRange(obstacles.Select(p => new DebugCurve(100, 1, "brown", p))); // if (greenCurves != null) // l.AddRange(greenCurves.Select(p => new DebugCurve(100, 10, "navy", p))); // if (redCurves != null) // l.AddRange(redCurves.Select(p => new DebugCurve(100, 10, "red", p))); // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(l); // } // #endif void TryToCreateNewEdgeAndSetIsPassable(VisibilityEdge edge, Shape looseShape) { var e = visGraph.FindEdge(edge.SourcePoint, edge.TargetPoint); if (e != null) return; e = visGraph.AddEdge(edge.SourcePoint, edge.TargetPoint); if (looseShape != null) e.IsPassable = () => looseShape.IsTransparent; } void AddBoundaryEdgesToVisGraph(Polyline boundary) { if (boundary == null) return; var p = boundary.StartPoint; do { var pn = p.NextOnPolyline; visGraph.AddEdge(p.Point, pn.Point); if (pn == boundary.StartPoint) break; p = pn; } while (true); } Set RemoveInsidePortsAndSplitBoundaryIfNeeded(Polyline boundary) { var ret = new Set(); if (boundary == null) { foreach (var point in portRTree.GetAllLeaves()) ret.Insert(point); portRTree.Clear(); return ret; } Rectangle boundaryBox = boundary.BoundingBox; var portLocationsInQuestion = portRTree.GetAllIntersecting(boundaryBox).ToArray(); foreach (var point in portLocationsInQuestion) { switch (Curve.PointRelativeToCurveLocation(point, boundary)) { case PointLocation.Inside: ret.Insert(point); portLocationsToLoosePolylines[point] = boundary; portRTree.Remove(new Rectangle(point), point); break; case PointLocation.Boundary: portRTree.Remove(new Rectangle(point), point); portLocationsToLoosePolylines[point] = boundary; PolylinePoint polylinePoint = FindPointOnPolylineToInsertAfter(boundary, point); if (polylinePoint != null) LineSweeper.InsertPointIntoPolylineAfter(boundary, polylinePoint, point); else throw new InvalidOperationException(); break; } } return ret; } static PolylinePoint FindPointOnPolylineToInsertAfter(Polyline boundary, Point point) { for (PolylinePoint p = boundary.StartPoint; ; ) { PolylinePoint pn = p.NextOnPolyline; if (ApproximateComparer.Close(point, p.Point) || ApproximateComparer.Close(point, pn.Point)) return null; //the point is already inside double par; if (ApproximateComparer.Close(Point.DistToLineSegment(point, p.Point, pn.Point, out par), 0)) return p; p = pn; if (p == boundary.StartPoint) throw new InvalidOperationException(); } } /// /// creates a root; a shape with BoundaryCurve set to null /// void GetOrCreateRoot() { if (rootShapes.Count() == 0) return; if (rootShapes.Count() == 1) { Shape r = rootShapes.First(); if (r.BoundaryCurve == null) { root = r; return; } } rootWasCreated = true; root = new Shape(); #if TEST_MSAGL root.UserData = "root"; #endif foreach (var rootShape in rootShapes) root.AddChild(rootShape); } void RemoveRoot() { if (rootWasCreated) foreach (var rootShape in rootShapes) rootShape.RemoveParent(root); } // #if TEST_MSAGL // // ReSharper disable UnusedMember.Local // [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // static void Show( // IEnumerable edgeGeometries, IEnumerable listOfShapes) { // // ReSharper restore UnusedMember.Local // var r = new Random(1); // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration( // listOfShapes.Select(s => s.BoundaryCurve).Select( // c => new DebugCurve(50, 1, DebugCurve.Colors[r.Next(DebugCurve.Colors.Length - 1)], c)).Concat( // edgeGeometries.Select(e => new DebugCurve(100, 1, "red", e.Curve)))); // } // #endif internal static Dictionary> GetAncestorSetsMap(IEnumerable shapes) { var ancSets = new Dictionary>(); foreach (var child in shapes.Where(child => !ancSets.ContainsKey(child))) ancSets[child] = GetAncestorSet(child, ancSets); return ancSets; } static Set GetAncestorSet(Shape child, Dictionary> ancSets) { var ret = new Set(child.Parents); foreach (var parent in child.Parents) { Set grandParents; ret += ancSets.TryGetValue(parent, out grandParents) ? grandParents : ancSets[parent] = GetAncestorSet(parent, ancSets); } return ret; } static internal void CreatePortsIfNeeded(IEnumerable edges) { foreach (var edge in edges) { if (edge.SourcePort == null) { var e = edge; #if SHARPKIT // Lambdas bind differently in JS edge.SourcePort = ((Func)(ed => new RelativeFloatingPort(() => ed.Source.BoundaryCurve, () => ed.Source.Center)))(e); #else edge.SourcePort = new RelativeFloatingPort(() => e.Source.BoundaryCurve, () => e.Source.Center); #endif } if (edge.TargetPort == null) { var e = edge; #if SHARPKIT // Lambdas bind differently in JS edge.TargetPort = ((Func)(ed => new RelativeFloatingPort(() => ed.Target.BoundaryCurve, () => ed.Target.Center)))(e); #else edge.TargetPort = new RelativeFloatingPort(() => e.Target.BoundaryCurve, () => e.Target.Center); #endif } } } /// /// computes loosePadding for spline routing obstacles from node separation and EdgePadding. /// /// /// /// static public double ComputeLooseSplinePadding(double nodeSeparation, double edgePadding) { Debug.Assert(edgePadding > 0, "require EdgePadding > 0"); double twicePadding = 2.0 * edgePadding; Debug.Assert(nodeSeparation > twicePadding, "require OverlapSeparation > 2*EdgePadding"); // the 8 divisor is just to guarantee the final postcondition double loosePadding = (nodeSeparation - twicePadding) / 8; Debug.Assert(loosePadding > 0, "require LoosePadding > 0"); Debug.Assert(twicePadding + (2 * loosePadding) < nodeSeparation, "EdgePadding too big!"); return loosePadding; } } }