using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Msagl.Core.DataStructures; using Microsoft.Msagl.Core.Geometry; using Microsoft.Msagl.Core.Geometry.Curves; using Microsoft.Msagl.Core.Layout; using Microsoft.Msagl.Core.ProjectionSolver; using Microsoft.Msagl.DebugHelpers; using Microsoft.Msagl.Routing.Visibility; namespace Microsoft.Msagl.Routing.Rectilinear.Nudging { /// /// following paper "Orthogonal Connector Routing" which is included in the project /// #if TEST_MSAGL public #else internal #endif class Nudger { bool HasGroups { get { return (null != HierarchyOfGroups) && (HierarchyOfGroups.Count > 0); } } Dictionary axisEdgesToObstaclesTheyOriginatedFrom; List Paths { get; set; } IEnumerable Obstacles { get; set; } internal VisibilityGraph PathVisibilityGraph { get; set; } /// /// "nudge" paths to decrease the number of intersections and stores the results inside WidePaths of "paths" /// /// paths through the graph /// two parallel paths should be separated by this distance if it is feasible /// polygonal convex obstacles organized in a tree; the obstacles here are padded original obstacles /// /// internal Nudger(IEnumerable paths, double cornerFitRad, IEnumerable obstacles, Dictionary> ancestorsSets) { AncestorsSets = ancestorsSets; HierarchyOfGroups = RectangleNode.CreateRectangleNodeOnEnumeration( ancestorsSets.Keys.Where(shape => shape.IsGroup).Select(group => new RectangleNode(group, group.BoundingBox))); Obstacles = obstacles; EdgeSeparation = 2 * cornerFitRad; Paths = new List(paths); HierarchyOfObstacles = RectangleNode.CreateRectangleNodeOnEnumeration( obstacles.Select(p => new RectangleNode(p, p.BoundingBox))); MapPathsToTheirObstacles(); } Dictionary> AncestorsSets { get; set; } void MapPathsToTheirObstacles() { PathToObstacles = new Dictionary>(); foreach (var path in Paths) MapPathToItsObstacles(path); } void MapPathToItsObstacles(Path path) { var startNode = HierarchyOfObstacles.FirstHitNode(path.PathPoints.First(),ObstacleTest); var endNode = HierarchyOfObstacles.FirstHitNode(path.PathPoints.Last(), ObstacleTest); if ((null != startNode) && (null != endNode)) { PathToObstacles[path] = new Tuple(startNode.UserData, endNode.UserData); } } static HitTestBehavior ObstacleTest(Point pnt, Polyline polyline) { return (Curve.PointRelativeToCurveLocation(pnt, polyline) != PointLocation.Outside) ? HitTestBehavior.Stop : HitTestBehavior.Continue; } /// /// /// protected RectangleNode HierarchyOfObstacles { get; set; } /// /// /// protected RectangleNode HierarchyOfGroups { get; set; } internal void Calculate(Directions direction, bool mergePaths) { NudgingDirection = direction; PathRefiner.RefinePaths(Paths, mergePaths); // ShowPathsDebug(Paths); GetPathOrdersAndPathGraph(); MapAxisEdgesToTheirObstacles(); DrawPaths(); //ShowPathsDebug(Paths); } void MapAxisEdgesToTheirObstacles() { axisEdgesToObstaclesTheyOriginatedFrom = new Dictionary(); foreach (var path in Paths) MapPathEndAxisEdgesToTheirObstacles(path); //The assignment above was too greedy. An edge belonging to interiour edges of some path can be marked by mistake. foreach (var path in Paths) UmmapPathInteriourFromStrangerObstacles(path); } void UmmapPathInteriourFromStrangerObstacles(Path path) { var firstUnmappedEdge = FindFirstUnmappedEdge(path); if(firstUnmappedEdge==null) return; var lastUnmappedEdge = FindLastUnmappedEdge(path); for (var edge = firstUnmappedEdge; edge != null && edge != lastUnmappedEdge; edge = edge.Next) axisEdgesToObstaclesTheyOriginatedFrom.Remove(edge.AxisEdge); } PathEdge FindLastUnmappedEdge(Path path) { for (var edge = path.LastEdge; edge != null; edge = edge.Prev) if (edge.AxisEdge.Direction != NudgingDirection) return edge; return null; } PathEdge FindFirstUnmappedEdge(Path path) { for (var edge = path.FirstEdge; edge != null; edge = edge.Next) if (edge.AxisEdge.Direction != NudgingDirection) return edge; return null; } void MapPathEndAxisEdgesToTheirObstacles(Path path) { Tuple coupleOfObstacles; if (PathToObstacles.TryGetValue(path, out coupleOfObstacles)) { ProcessThePathStartToMapAxisEdgesToTheirObstacles(path, coupleOfObstacles.Item1); ProcessThePathEndToMapAxisEdgesToTheirObstacles(path, coupleOfObstacles.Item2); } } void ProcessThePathEndToMapAxisEdgesToTheirObstacles(Path path, Polyline endPolyline) { for (var edge = path.LastEdge; edge != null && CompassVector.DirectionsAreParallel(edge.Direction, NudgingDirection); edge = edge.Prev) axisEdgesToObstaclesTheyOriginatedFrom[edge.AxisEdge] = endPolyline; } void ProcessThePathStartToMapAxisEdgesToTheirObstacles(Path path, Polyline startPolyline) { for (var edge = path.FirstEdge; edge != null && CompassVector.DirectionsAreParallel(edge.Direction, NudgingDirection); edge = edge.Next) axisEdgesToObstaclesTheyOriginatedFrom[edge.AxisEdge] = startPolyline; //possible bug here because an edge might ignore two obstacles if it connects them } void GetPathOrdersAndPathGraph() { var combinatorialNudger = new CombinatorialNudger(Paths); PathOrders = combinatorialNudger.GetOrder(); PathVisibilityGraph = combinatorialNudger.PathVisibilityGraph; } /* [Conditional("DEBUG")] void CheckPathsForSwitchbacks() { foreach(var p in Paths) CheckForSwitchbacks(p.PathPoints.ToArray()); } */ Directions NudgingDirection { get; set; } #region debugging #if TEST_MSAGL static internal ICurve[] GetCurvesForShow(IEnumerable paths, IEnumerable obstacles) { var ret = new List(); foreach (var path in paths) { var poly = new Polyline(); foreach (var point in path.PathPoints) poly.AddPoint(point); ret.Add(poly); } ret.AddRange(obstacles.Cast()); return ret.ToArray(); } #endif #endregion void DrawPaths() { SetWidthsOfArrowheads(); CreateLongestNudgedSegments(); FindFreeSpaceInDirection(PathVisibilityGraph.Edges.Cast()); MoveLongestSegsIdealPositionsInsideFeasibleIntervals(); PositionShiftedEdges(); } void SetWidthsOfArrowheads() { foreach (Path edgePath in Paths) SetWidthsOfArrowheadsForEdge(edgePath); } static void SetWidthsOfArrowheadsForEdge(Path path) { var edgeGeom = path.EdgeGeometry; if (edgeGeom.TargetArrowhead != null) { PathEdge pathEdge = path.LastEdge; pathEdge.Width = Math.Max(edgeGeom.TargetArrowhead.Width, pathEdge.Width); } if (edgeGeom.SourceArrowhead != null) { PathEdge pathEdge = path.FirstEdge; pathEdge.Width = Math.Max(edgeGeom.SourceArrowhead.Width, pathEdge.Width); } } internal double EdgeSeparation { get; set; } void PositionShiftedEdges() { //we are using 2*cornerFitRadius for the minimal edge separation Solver = new UniformOneDimensionalSolver(EdgeSeparation); foreach (var segment in LongestNudgedSegs) CreateVariablesOfLongestSegment(segment); CreateConstraintsOfTheOrder(); CreateConstraintsBetweenLongestSegments(); Solver.Solve(); ShiftPathEdges(); } void MoveLongestSegsIdealPositionsInsideFeasibleIntervals() { foreach (var seg in LongestNudgedSegs) MoveLongestSegIdealPositionsInsideFeasibleInterval(seg); } static void MoveLongestSegIdealPositionsInsideFeasibleInterval(LongestNudgedSegment seg) { if (seg.IsFixed) return; var leftBound = seg.GetLeftBound(); var rightBound = seg.GetRightBound(); if (seg.IdealPosition < leftBound) seg.IdealPosition=leftBound; else if( seg.IdealPosition> rightBound) seg.IdealPosition=rightBound; } void ShiftPathEdges() { foreach (var path in Paths) path.PathPoints = GetShiftedPoints(path).ToArray(); } IEnumerable GetShiftedPoints(Path path) { return RemoveSwitchbacksAndMiddlePoints(GetShiftedPointsSimple(path)); } /// /// sometimes we have very small mistakes in the positions that have to be fixed /// /// static Point Rectilinearise(Point a, Point b) { #if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=369 there are no structs in js b = b.Clone(); #endif if (a.X != b.X && a.Y != b.Y) { var dx = Math.Abs(a.X - b.X); var dy = Math.Abs(a.Y - b.Y); if (dx < dy) b.X = a.X; else b.Y = a.Y; } return b; } IEnumerable GetShiftedPointsSimple(Path path) { var edge = path.FirstEdge; yield return ShiftedPoint(edge.Source, edge.LongestNudgedSegment); foreach (var e in path.PathEdges) yield return ShiftedEdgePositionOfTarget(e); } Point ShiftedEdgePositionOfTarget(PathEdge e) { return e.LongestNudgedSegment != null || e.Next == null ? ShiftedPoint(e.Target, e.LongestNudgedSegment) : ShiftedPoint(e.Next.Source, e.Next.LongestNudgedSegment); } Point ShiftedPoint(Point point, LongestNudgedSegment segment) { if (segment == null) return point; var t = Solver.GetVariablePosition(segment.Id); return NudgingDirection == Directions.North ? new Point(t, point.Y) : new Point(point.X, -t); } #region debug // #if TEST_MSAGL // internal static void ShowPathsFromPoints(IEnumerable paths, IEnumerable enumerable) { // var dd = new List(); // if (enumerable != null) // dd.AddRange(GetObstacleBoundaries(enumerable, "grey")); // int i = 0; // foreach (var p in paths) // dd.AddRange(PathDebugCurvesFromPoints(p, DebugCurve.Colors[Math.Min(DebugCurve.Colors.Length, i++)])); // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(dd); // } // // static IEnumerable PathDebugCurvesFromPoints(Path path, string color) { // const double startWidth = 0.01; // const double endWidth = 3; // // var pts = path.PathPoints.ToArray(); // double delta = (endWidth - startWidth) / (pts.Length - 1); // for (int i = 0; i < pts.Length - 1; i++) // yield return new DebugCurve(startWidth + delta * i, color, new LineSegment(pts[i], pts[i + 1])); // } // // internal static void ShowParamPaths( Point s, Point e, params Path[] paths ) { // ShowOrderedPaths(null,paths,s,e); // } // // // ReSharper disable UnusedMember.Local // internal static void ShowOrderedPaths(IEnumerable obstacles, IEnumerable paths, Point s, Point e) { // // ReSharper restore UnusedMember.Local // string[] colors = { "red", "green", "blue", "violet", "rose", "black" }; // // const double startWidth = 0.001; // const double endWidth = 0.1; // var dd = new List(); // if (obstacles != null) // dd.AddRange(GetObstacleBoundaries(obstacles, "grey")); // int i = 0; // foreach (var path in paths) // dd.AddRange(GetTestPathAsDebugCurve(startWidth, endWidth, colors[Math.Min(colors.Length - 1, i++)], path)); // // var ell = new DebugCurve(1, "black", new Ellipse(0.01, 0.01, s)); // dd.Add(ell); // dd.Add(new DebugCurve(1, "black", new Ellipse(0.02, 0.02, e))); // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(dd.Concat(GetObstacleBoundaries(obstacles, "lightblue"))); // } // // static IEnumerable GetTestPathAsDebugCurve(double startWidth, double endWidth, string color, Path path) { // if (path.PathEdges.Count() > 0) { // int count = path.PathEdges.Count(); // // double deltaW = count > 1 ? (endWidth - startWidth)/(count - 1) : 1; // //if count ==1 the value of deltaW does not matter // int i = 0; // foreach (var e in path.PathEdges) // yield return // new DebugCurve(150, startWidth + deltaW*(i++), color, new LineSegment(e.Source, e.Target)); // }else { // int count = path.PathPoints.Count(); // var pts = path.PathPoints.ToArray(); // // var deltaW = count > 1 ? (endWidth - startWidth) / (count - 1) : 1; // //if count ==1 the value of deltaW does not matter // for (int i = 0; i < count - 1;i++ ) // yield return new DebugCurve(150, startWidth+deltaW*i, color, new LineSegment(pts[i], pts[i+1])); // } // } // // internal static IEnumerable GetTestEdgePathAsDebugCurves(double startWidth, double endWidth, string color, Path path) { // int count = path.PathPoints.Count(); // // double deltaW = count > 1 ? (endWidth - startWidth) / (count - 1) : 1; //if count ==1 the value of deltaW does not matter // var points = path.PathPoints.ToArray(); // for (int i = 0; i < points.Length - 1; i++) // yield return new DebugCurve(125, startWidth + deltaW * i, color, new LineSegment(points[i], points[i + 1])); // } // // [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // static IEnumerable GetEdgePathFromPathEdgesAsDebugCurves(double startWidth, double endWidth, string color, Path path) { // var points = path.PathPoints.ToArray(); // // int count = points.Length; // // double deltaW = count > 1 ? (endWidth - startWidth) / (count - 1) : 1; //if count ==1 the value of deltaW does not matter // for (int i = 0; i < points.Length - 1; i++) // yield return new DebugCurve(120, startWidth + deltaW * i, color, new LineSegment(points[i], points[i + 1])); // } // // ReSharper disable UnusedMember.Local // // static internal void ShowEdgePaths(IEnumerable obstacles, IEnumerable edgePaths) { // // ReSharper restore UnusedMember.Local // List debCurves = GetDebCurvesOfPaths(obstacles, edgePaths); // // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(debCurves); // } // // internal static List GetDebCurvesOfPaths(IEnumerable enumerable, IEnumerable edgePaths) { // var debCurves = GetObstacleBoundaries(enumerable, "black"); // int i = 0; // // foreach (var edgePath in edgePaths) // debCurves.AddRange(GetTestEdgePathAsDebugCurves(0.2, 4, DebugCurve.Colors[(i++) % DebugCurve.Colors.Length], edgePath)); // return debCurves; // } // // internal static void ShowPathsInLoop(IEnumerable enumerable, IEnumerable edgePaths, Point point) { // foreach (var edgePath in edgePaths.Where(path=>(path.PathPoints.First()-point).Length<1 ||(path.PathPoints.Last()-point).Length<1 )) { // var debCurves = GetObstacleBoundaries(enumerable, "black"); // debCurves.AddRange(GetTestEdgePathAsDebugCurves(0.1, 4, "red", edgePath)); // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(debCurves); // } // } // // // // ReSharper disable UnusedMember.Local // [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // void ShowLongSegsWithIdealPositions(Directions dir) { // // ReSharper restore UnusedMember.Local // var debCurves = GetObstacleBoundaries(Obstacles, "black"); // int i = 0; // // debCurves.AddRange(LongestNudgedSegs.Select(ls => DebugCurveOfLongSeg(ls, DebugCurve.Colors[i++ % DebugCurve.Colors.Length], dir))); // // DebugCurveCollection.WriteToFile(debCurves, "c:/tmp/longSegs"); // // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(debCurves); // } // // static DebugCurve DebugCurveOfLongSeg(LongestNudgedSegment ls, string s, Directions dir) { // return new DebugCurve(1, s, LineSegOfLongestSeg(ls, dir)); // } // // static ICurve LineSegOfLongestSeg(LongestNudgedSegment ls, Directions dir) { // var projectionToDir = dir == Directions.East ? (PointProjection)(p => p.X) : (p => p.Y); // var min = Double.PositiveInfinity; // var max = Double.NegativeInfinity; // foreach (var edge in ls.Edges) { // UpdateMinMaxWithPoint(ref min, ref max, projectionToDir, edge.Source); // UpdateMinMaxWithPoint(ref min, ref max, projectionToDir, edge.Target); // } // return dir == Directions.East // ? new LineSegment(min, -ls.IdealPosition, max, -ls.IdealPosition) // : new LineSegment(ls.IdealPosition, min, ls.IdealPosition, max); // } // // static void UpdateMinMaxWithPoint(ref double min, ref double max, PointProjection projectionToDir, Point point) { // double p = projectionToDir(point); // if (min > p) // min = p; // if (max < p) // max = p; // } // // // // ReSharper disable UnusedMember.Local // [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // void ShowPathsDebug(IEnumerable edgePaths) { // var debCurves = GetObstacleBoundaries(Obstacles, "black"); // int i = 0; // // foreach (var edgePath in edgePaths) // debCurves.AddRange(GetEdgePathFromPathEdgesAsDebugCurves(0.01, 0.4, DebugCurve.Colors[(i++) % DebugCurve.Colors.Length], edgePath)); // // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(debCurves); // } // // [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // static IEnumerable PathDebugCurves(Path path, string color) { // var d = path.PathEdges.Select(e => new DebugCurve(70, 0.5, color, new LineSegment(e.Source, e.Target))); // return d.Concat(MarkPathVerts(path)); // } // // [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // private static IEnumerable MarkPathVerts(Path path) { // bool first = true; // var p = new Point(); // foreach (var p0 in path.PathPoints) { // if (first) { // yield return new DebugCurve(200, 1, "violet", CurveFactory.CreateDiamond(5, 5, p0)); // first = false; // } else // yield return new DebugCurve(100, 0.5, "brown", CurveFactory.CreateEllipse(1.5, 1.5, p0)); // p = p0; // } // yield return new DebugCurve(200, 1, "green", CurveFactory.CreateDiamond(3, 3, p)); // } // // static internal IEnumerable PathDebugCurvesFromPoint(Path path) { // var l = new List(path.PathPoints); // for (int i = 0; i < l.Count - 1; i++) // yield return new DebugCurve(4, "red", new LineSegment(l[i], l[i + 1])); // } // // // // ReSharper disable UnusedMember.Local // // void ShowEdgesOfEdgePath(Path path){ // // ReSharper restore UnusedMember.Local // // string[] colors = {"red", "brown", "purple"}; // // const double w0 = 1; // // const double w1 = 3; // // double dw = (w1 - w0)/path.OrientedSubpaths.Count; // // int i = 0; // // var dc = new List(); // // foreach (var s in path.OrientedSubpaths){ // // dc.AddRange(SubpathDebugCurves(w0 + dw*i, colors[Math.Min(i++, colors.Length - 1)], s)); // // } // // LayoutAlgorithmSettings.ShowDebugCurves(dc.ToArray()); // // } // // // // static IEnumerable SubpathDebugCurves(double w, string color, OrientedSubpath subpath){ // // return subpath.LinkedPath.Select(e => new DebugCurve(w, color, new LineSegment(e.Source.Point, e.Target.Point))); // // } // // // static internal List GetObstacleBoundaries(IEnumerable obstacles, string color) { // var debugCurves = new List(); // if (obstacles != null) // debugCurves.AddRange(obstacles.Select(poly => new DebugCurve(50, 0.3, color, poly))); // return debugCurves; // } // #endif #endregion void CreateConstraintsBetweenLongestSegments() { foreach (var segment in LongestNudgedSegs) CreateConstraintsBetweenLongestSegmentsForSegment(segment); } void CreateConstraintsBetweenLongestSegmentsForSegment(LongestNudgedSegment segment) { var rightNeighbors = new Set(); foreach (var pathEdge in segment.Edges) { var axisEdge = pathEdge.AxisEdge; if (axisEdge != null) foreach (var rightNeiAxisEdge in axisEdge.RightNeighbors) foreach (var longSeg in rightNeiAxisEdge.LongestNudgedSegments) rightNeighbors.Insert(longSeg); } foreach (var seg in rightNeighbors) ConstraintTwoLongestSegs(segment, seg); } void CreateConstraintsOfTheOrder() { foreach (var kv in PathOrders) if (ParallelToDirection(kv.Key, NudgingDirection)) CreateConstraintsOfThePathOrder(kv.Value); } static bool ParallelToDirection(VisibilityEdge edge, Directions direction) { switch (direction) { case Directions.North: case Directions.South: return ApproximateComparer.Close(edge.SourcePoint.X, edge.TargetPoint.X); default: return ApproximateComparer.Close(edge.SourcePoint.Y, edge.TargetPoint.Y); } } void CreateConstraintsOfThePathOrder(IEnumerable pathOrder) { PathEdge prevEdge = null; foreach (var pathEdge in pathOrder.Where(p => p.LongestNudgedSegment != null)) { if (prevEdge != null) ConstraintTwoLongestSegs(prevEdge.LongestNudgedSegment, pathEdge.LongestNudgedSegment); prevEdge = pathEdge; } } void ConstraintTwoLongestSegs(LongestNudgedSegment prevSeg, LongestNudgedSegment seg) { if (!prevSeg.IsFixed || !seg.IsFixed) Solver.AddConstraint(prevSeg.Id, seg.Id); } UniformOneDimensionalSolver Solver { get; set; } void CreateVariablesOfLongestSegment(LongestNudgedSegment segment) { if (!segment.IsFixed) { var leftBound = segment.GetLeftBound(); var rightBound = segment.GetRightBound(); if (leftBound >= rightBound) {//don't move the segment from the way it was generated Solver.AddFixedVariable(segment.Id, SegmentPosition(segment, NudgingDirection)); segment.IsFixed = true; } else { Solver.AddVariable(segment.Id, SegmentPosition(segment, NudgingDirection), segment.IdealPosition, segment.Width); // Debug.Assert(leftBound + Curve.DistanceEpsilon < rightBound); //this assert does not hold for overlaps if (leftBound != Double.NegativeInfinity) Solver.SetLowBound(leftBound, segment.Id); if (rightBound != Double.PositiveInfinity) Solver.SetUpperBound(segment.Id, rightBound); } } else Solver.AddFixedVariable(segment.Id, SegmentPosition(segment, NudgingDirection)); } static double SegmentPosition(SegmentBase segment, Directions direction) { return direction == Directions.North ? segment.Start.X : -segment.Start.Y; } // #if TEST_MSAGL // // ReSharper disable UnusedMember.Local // [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // void ShowSegmentBounds(LongestNudgedSegment segment) { // // ReSharper restore UnusedMember.Local // var dd = GetObstacleBoundaries(Obstacles, "black"); // var segtop = segment.Edges.Max(e => Math.Max(e.Source.Y, e.Target.Y)); // var segbottom = segment.Edges.Min(e => Math.Min(e.Source.Y, e.Target.Y)); // var segx = segment.Start.X; // var seg = new DebugCurve(80, 1, "brown", new LineSegment(new Point(segx, segbottom), new Point(segx, segtop))); // // var leftbound = new DebugCurve(80, 1.0, "red", new LineSegment(new Point(segment.GetLeftBound(), segbottom), // new Point(segment.GetLeftBound(), segtop))); // var rightbound = new DebugCurve(80, 1, "green", new LineSegment(new Point(segment.GetRightBound(), segbottom), // new Point(segment.GetRightBound(), segtop))); // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(dd.Concat(new[] { seg, leftbound, rightbound })); // } // // ReSharper disable UnusedMember.Local // [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // void ShowSegment(LongestNudgedSegment segment) { // // ReSharper restore UnusedMember.Local // var dd = GetObstacleBoundaries(Obstacles, "black"); // var segtop = segment.Edges.Max(e => Math.Max(e.Source.Y, e.Target.Y)); // var segbottom = segment.Edges.Min(e => Math.Min(e.Source.Y, e.Target.Y)); // var segx = segment.Start.X; // var seg = new DebugCurve(80, 1, "brown", new LineSegment(new Point(segx, segbottom), new Point(segx, segtop))); // // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(dd.Concat(new[] { seg })); // // } // #endif List LongestNudgedSegs { get; set; } Dictionary> PathOrders { get; set; } /// /// maps each path to the pair of obstacles; the first element of the pair is /// where the path starts and the second where the path ends /// Dictionary> PathToObstacles { get; set; } void FindFreeSpaceInDirection(IEnumerable axisEdges) { BoundAxisEdgesByRectsKnownInAdvance(); var freeSpaceFinder = new FreeSpaceFinder(NudgingDirection, Obstacles, axisEdgesToObstaclesTheyOriginatedFrom, PathOrders, axisEdges); freeSpaceFinder.FindFreeSpace(); } void BoundAxisEdgesByRectsKnownInAdvance() { foreach (var path in Paths) { if (HasGroups) BoundPathByMinCommonAncestors(path); BoundAxisEdgesAdjacentToSourceAndTargetOnEdge(path); } } void BoundPathByMinCommonAncestors(Path path) { foreach (var rect in GetMinCommonAncestors(path.EdgeGeometry).Select(sh => sh.BoundingBox)) foreach ( var edge in path.PathEdges.Select(e => e.AxisEdge).Where( axisEdge => axisEdge.Direction == NudgingDirection) ) BoundAxisEdgeByRect(rect, edge); } IEnumerable GetMinCommonAncestors(EdgeGeometry edgeGeometry) { if (PortToShapes == null) PortToShapes = MapPortsToShapes(AncestorsSets.Keys); var commonAncestors = AncestorsForPort(edgeGeometry.SourcePort)* AncestorsForPort(edgeGeometry.TargetPort); return commonAncestors.Where(anc => !anc.Children.Any(child=>commonAncestors.Contains(child))); } /// /// /// protected Dictionary PortToShapes { get; private set; } Set AncestorsForPort(Port port) { Shape shape; if (PortToShapes.TryGetValue(port, out shape)) { return AncestorsSets[shape]; } // This is a FreePort or Waypoint; return all spatial parents. return new Set(HierarchyOfGroups.AllHitItems(new Rectangle(port.Location, port.Location), null)); } void BoundAxisEdgeAdjacentToObstaclePort(Port port, AxisEdge axisEdge) { if (port is WaypointPort || (port.Curve == null && port.PortEntry == null)) BoundAxisByPoint(port.Location, axisEdge); else if (port.PortEntry == null) { if (port.Curve.BoundingBox.Contains(port.Location)) BoundAxisEdgeByRect(port.Curve.BoundingBox, axisEdge); } else { var portEntry = port.PortEntry as PortEntryOnCurve; if (portEntry != null) { Rectangle rect; if (FindPortEntryRectCrossingAxisEdge(portEntry, axisEdge, out rect)) BoundAxisEdgeByRect(rect, axisEdge); } } } void BoundAxisByPoint(Point point, AxisEdge axisEdge) { if (axisEdge != null && axisEdge.Direction == NudgingDirection) if (NudgingDirection == Directions.North) { axisEdge.BoundFromLeft(point.X); axisEdge.BoundFromRight(point.X); } else { axisEdge.BoundFromLeft(-point.Y); axisEdge.BoundFromRight(-point.Y); } } static bool FindPortEntryRectCrossingAxisEdge(PortEntryOnCurve portEntry, AxisEdge axisEdge, out Rectangle rect) { var ar = new Rectangle(axisEdge.SourcePoint, axisEdge.TargetPoint); foreach (Rectangle r in portEntry.AllowedRectangles) { if(r.Intersects(ar)) { rect = r; return true; } } rect = Rectangle.CreateAnEmptyBox(); return false; } void BoundAxisEdgesAdjacentToSourceAndTargetOnEdge(Path path) { BoundAxisEdgeAdjacentToObstaclePort(path.EdgeGeometry.SourcePort, path.FirstEdge.AxisEdge); BoundAxisEdgeAdjacentToObstaclePort(path.EdgeGeometry.TargetPort, path.LastEdge.AxisEdge); } void BoundAxisEdgeByRect(Rectangle rectangle, AxisEdge axisEdge) { if (axisEdge != null && axisEdge.Direction == NudgingDirection) if (NudgingDirection == Directions.North) { axisEdge.BoundFromLeft(rectangle.Left); axisEdge.BoundFromRight(rectangle.Right); } else { axisEdge.BoundFromLeft(-rectangle.Top); axisEdge.BoundFromRight(-rectangle.Bottom); } } void CreateLongestNudgedSegments() { var projectionToPerp = NudgingDirection == Directions.East ? (PointProjection)FreeSpaceFinder.MinusY : FreeSpaceFinder.X; LongestNudgedSegs = new List(); foreach (var path in Paths) CreateLongestNudgedSegmentsForPath(path, projectionToPerp); } void CreateLongestNudgedSegmentsForPath(Path path, PointProjection projectionToPerp) { //ShowEdgesOfEdgePath(path); GoOverPathAndCreateLongSegs(path); CalculateIdealPositionsForLongestSegs(path, projectionToPerp); } static void CalculateIdealPositionsForLongestSegs(Path path, PointProjection projectionToPerp) { LongestNudgedSegment currentLongSeg = null; LongestNudgedSegment ret = null; double prevOffset = projectionToPerp(path.Start); foreach (var edge in path.PathEdges) { if (edge.LongestNudgedSegment != null) { currentLongSeg = edge.LongestNudgedSegment; if (ret != null) { double t; SetIdealPositionForSeg(ret, t = projectionToPerp(ret.Start), prevOffset, projectionToPerp(currentLongSeg.Start)); prevOffset = t; ret = null; } } else if (currentLongSeg != null) { ret = currentLongSeg; currentLongSeg = null; } } if (ret != null) SetIdealPositionForSeg(ret, projectionToPerp(ret.Start), prevOffset, projectionToPerp(path.End)); else if (currentLongSeg != null) currentLongSeg.IdealPosition = projectionToPerp(currentLongSeg.Start); } static void SetIdealPositionForSeg(LongestNudgedSegment segment, double segPosition, double offset0, double offset1) { var max = Math.Max(offset0, offset1); var min = Math.Min(offset0, offset1); if (min + ApproximateComparer.DistanceEpsilon < segPosition) if (segPosition < max) segment.IdealPosition = 0.5 * (max + min); else segment.IdealPosition = max; else segment.IdealPosition = min; } void GoOverPathAndCreateLongSegs(Path path) { LongestNudgedSegment currentLongestSeg = null; var oppositeDir = CompassVector.OppositeDir(NudgingDirection); foreach (var edge in path.PathEdges) { var edgeDir = edge.Direction; if (edgeDir == NudgingDirection || edgeDir == oppositeDir) { if (currentLongestSeg == null) #if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=368 { edge.LongestNudgedSegment = currentLongestSeg = new LongestNudgedSegment(LongestNudgedSegs.Count); LongestNudgedSegs.Add(edge.LongestNudgedSegment); } #else LongestNudgedSegs.Add( edge.LongestNudgedSegment = currentLongestSeg = new LongestNudgedSegment(LongestNudgedSegs.Count)); #endif else edge.LongestNudgedSegment = currentLongestSeg; if (edge.IsFixed) currentLongestSeg.IsFixed = true; } else { //the edge is perpendicular to "direction" edge.LongestNudgedSegment = null; currentLongestSeg = null; } } } static IEnumerable BuildPolylineForPath(Path path) { #if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=369 var points = path.PathPoints.Select(p=>p.Clone()).ToArray(); #else var points = path.PathPoints.ToArray(); #endif ExtendPolylineToPorts(ref points, path); for(int i=0;i RemoveSwitchbacksAndMiddlePoints(IEnumerable points) { var en = points.GetEnumerator(); en.MoveNext(); var a = en.Current; yield return a; en.MoveNext(); var b = en.Current; var prevDir = (b - a).CompassDirection; while (en.MoveNext()) { var dir = (en.Current - b).CompassDirection; if (dir == prevDir || CompassVector.OppositeDir(dir) == prevDir || dir == Directions.None) //we continue walking along the same straight line, maybe going backwards! b = en.Current; else { if (!ApproximateComparer.Close(a, b)) {//make sure that we are not returning the same point twice yield return a=Rectilinearise(a, b); } b = en.Current; prevDir = dir; } } if (!ApproximateComparer.Close(a, b)) yield return Rectilinearise(a,b); } /// /// this function defines the final path coordinates /// /// the set of paths, point sequences /// the radius of the arc inscribed into the path corners /// an enumeration of padded obstacles /// /// /// the mapping of the path to its modified path internal static void NudgePaths(IEnumerable paths, double cornerFitRadius, IEnumerable paddedObstacles, Dictionary> ancestorsSets, bool removeStaircases) { if (!paths.Any()) return; var nudger = new Nudger(paths, cornerFitRadius, paddedObstacles, ancestorsSets); nudger.Calculate(Directions.North, true); nudger.Calculate(Directions.East, false); nudger.Calculate(Directions.North, false); if (removeStaircases) nudger.RemoveStaircases(); foreach (var path in paths) path.EdgeGeometry.Curve = new Polyline(BuildPolylineForPath(path)); } void RemoveStaircases() { StaircaseRemover.RemoveStaircases(Paths, HierarchyOfObstacles); } /* void RemoveStaircasesFromPath(Path path) { var points = (Point[]) path.PathPoints; if (points.Length <= 4) return; int i = FindStaircase(points); if (i == -1) return; var linkedList = new LinkedList(points); var node = linkedList.First; for(int j=0;j FindStaircaseAfterNode(LinkedListNode n) { do { var nn = n.Next; var nnn = nn.Next; var nnnn = nnn.Next; if (nnn.Next == null) return null; if (IsStaircase(n.Value, nn.Value, nnn.Value, nnnn.Value)) return n; n = nn; } while (true); } bool IsStaircase(Point a, Point b, Point c, Point d) { return false; } void RemoveStaircaseOnNode(LinkedListNode node) { throw new NotImplementedException(); } int FindStaircase(Point[] points) { throw new NotImplementedException(); } */ internal static Dictionary MapPortsToShapes(IEnumerable listOfShapes) { var portToShapes = new Dictionary(); foreach (Shape shape in listOfShapes) foreach (Port port in shape.Ports) portToShapes[port] = shape; return portToShapes; } } }