using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Microsoft.Msagl.Core.DataStructures; using Microsoft.Msagl.Core.Geometry; using Microsoft.Msagl.Core.Geometry.Curves; using Microsoft.Msagl.Core.Layout; using Microsoft.Msagl.Core.Routing; using Microsoft.Msagl.Routing.Visibility; using Microsoft.Msagl.DebugHelpers; namespace Microsoft.Msagl.Routing.Spline.Bundling { /// /// Check intersections between hubs and obstacles with kd-tree /// internal class Intersections { readonly MetroGraphData metroGraphData; readonly BundlingSettings bundlingSettings; Func> obstaclesToIgnore; /// /// represents loose or tight hierarchy /// internal RectangleNode obstacleTree { get; set; } public Intersections(MetroGraphData metroGraphData, BundlingSettings bundlingSettings, RectangleNode obstacleTree, Func> obstaclesToIgnore) { this.metroGraphData = metroGraphData; this.obstaclesToIgnore = obstaclesToIgnore; this.bundlingSettings = bundlingSettings; this.obstacleTree = obstacleTree; } internal Set ObstaclesToIgnoreForBundle(Station node, Station adj) { if (node != null && adj != null) return obstaclesToIgnore(node) + obstaclesToIgnore(adj); if (node == null && adj == null) return new Set(); if (node != null) return obstaclesToIgnore(node); else return obstaclesToIgnore(adj); } #region Intersections with hub internal bool HubAvoidsObstacles(Station node, Point center, double upperBound, out List> touchedObstacles) { touchedObstacles = new List>(); double minimalDistance = upperBound; return IntersectCircleWithTree(obstacleTree, center, upperBound, obstaclesToIgnore(node), touchedObstacles, ref minimalDistance); } internal bool HubAvoidsObstacles(Point center, double upperBound, Set obstaclesToIgnore) { List> touchedObstacles; double minimalDistance; return HubAvoidsObstacles(center, upperBound, obstaclesToIgnore, out touchedObstacles, out minimalDistance); } internal double GetMinimalDistanceToObstacles(Station node, Point nodePosition, double upperBound) { List> touchedObstacles = new List>(); double minimalDistance = upperBound; if (!IntersectCircleWithTree(obstacleTree, nodePosition, upperBound, obstaclesToIgnore(node), touchedObstacles, ref minimalDistance)) return 0; return minimalDistance; } bool HubAvoidsObstacles(Point center, double upperBound, Set obstaclesToIgnore, out List> touchedObstacles, out double minimalDistance) { touchedObstacles = new List>(); minimalDistance = upperBound; return IntersectCircleWithTree(obstacleTree, center, upperBound, obstaclesToIgnore, touchedObstacles, ref minimalDistance); } /// /// Computes the intersection between the hub and obstacles /// /// /// /// /// these are the obstacles the are ignored by the method /// list of pairs (obstacle, closest point on the obstacle) /// min distance from the center to an obstacle /// false iff center is inside of an obstacle static bool IntersectCircleWithTree(RectangleNode node, Point center, double radius, Set obstaclesToIgnore, List> touchedObstacles, ref double minimalDistance) { if (!node.Rectangle.Contains(center, radius)) return true; if (node.UserData == null) { bool res = IntersectCircleWithTree(node.Left, center, radius, obstaclesToIgnore, touchedObstacles, ref minimalDistance); if (!res) return false; res = IntersectCircleWithTree(node.Right, center, radius, obstaclesToIgnore, touchedObstacles, ref minimalDistance); if (!res) return false; } else { Polyline obstacle = node.UserData; if (obstaclesToIgnore.Contains(obstacle)) return true; PointLocation pl = Curve.PointRelativeToCurveLocation(center, obstacle); if (pl != PointLocation.Outside) return false; Point touchPoint = obstacle[obstacle.ClosestParameter(center)]; double dist = (touchPoint - center).Length; if (dist <= radius) touchedObstacles.Add(new Tuple(obstacle, touchPoint)); minimalDistance = Math.Min(dist, minimalDistance); } return true; } #endregion /// /// faster way to check segment-polyline intersection /// NOTE: polyline points should be oriented clockwise /// internal static bool LineSegmentIntersectPolyline(Point start, Point end, Polyline poly) { Point segDirection = end - start; // the segment direction vector Debug.Assert(segDirection.Length > ApproximateComparer.DistanceEpsilon); double tStart = 0; // the maximum entering segment parameter double tEnd = 1; // the minimum leaving segment parameter foreach (var p in poly.PolylinePoints) { //process one edge var prev = p.PrevOnPolyline; Point e = prev.Point - p.Point; double num = Point.CrossProduct(e, start - p.Point); double den = -Point.CrossProduct(e, segDirection); if (Math.Abs(den) < ApproximateComparer.Tolerance) { // segment is nearly parallel to this edge if (num < 0) return false; continue; } //intersection parameter double t = num / den; if (den < 0) { // segment is entering across this edge tStart = Math.Max(tStart, t); // segment enters after leaving polygon if (tStart > tEnd) return false; } else { // segment is leaving across this edge tEnd = Math.Min(tEnd, t); // segment leaves before entering polygon if (tStart > tEnd) return false; } } return true; } static internal Polyline Create4gon(Point apex, Point baseCenter, double width1, double width2) { var norm = (baseCenter - apex).Normalize(); norm = new Point(norm.Y, -norm.X); return new Polyline(apex + norm * width1 / 2, apex - norm * width1 / 2, baseCenter - norm * width2 / 2, baseCenter + norm * width2 / 2) { Closed = true }; } // #if DEBUG && TEST_MSAGL // /// // /// check the validness of the drawing: // /// 1. hubs are not inside loose obstacles // /// 2. bundles do not cross loose obstacles // /// // internal bool HubPositionsAreOK() { // //check polylines // foreach (var line in metroGraphData.Metrolines) { // var poly = line.Polyline; // foreach (var p in poly.PolylinePoints) // Debug.Assert(metroGraphData.PointToStations.ContainsKey(p.Point)); // } // // foreach (var station in metroGraphData.Stations) { // // if (!station.IsRealNode && !HubAvoidsObstacles(station.Position, 0, obstaclesToIgnore(station))) { // if (LayoutAlgorithmSettings.ShowDebugCurvesEnumeration != null) { // HubDebugger.ShowHubs(metroGraphData, bundlingSettings, station); // ShowStationWithObstaclesToIgnore(station, obstacleTree.AllHitItems(station.Position)); // } // return false; // } // //bundles // foreach (var adj in station.Neighbors) { // if (ApproximateComparer.Close(adj.Position, station.Position)) // return false; // // if (!EdgeIsLegal(station, adj, station.Position, adj.Position)) { // if (LayoutAlgorithmSettings.ShowDebugCurvesEnumeration != null) { // //debug visualization // var l = new List(); // //foreach (var st in metroGraphData.Stations) { // // l.Add(new DebugCurve(100, 0.5, "grey", st.BoundaryCurve)); // //} // foreach (var poly in obstaclesToIgnore(station)) { // l.Add(new DebugCurve(100, 5, "green", poly)); // } // // foreach (var obstacle in obstacleTree.GetAllLeaves()) { // l.Add(new DebugCurve(100, 1, "red", obstacle)); // } // // l.Add(new DebugCurve(1, "blue", station.BoundaryCurve)); // l.Add(new DebugCurve(1, "blue", adj.BoundaryCurve)); // // l.Add(new DebugCurve(1, "blue", new LineSegment(adj.Position, adj.Neighbors.First().Position))); // l.Add(new DebugCurve(1, "blue", new LineSegment(station.Position, adj.Position))); // // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(l); // //end debug visualization // return false; // } // } // } // } // // return true; // } // // void ShowStationWithObstaclesToIgnore(Station station, IEnumerable allHitItems) { // var l = new List(); // foreach (var poly in allHitItems) { // l.Add(new DebugCurve(100, 0.5, "brown", poly)); // } // if (obstaclesToIgnore(station) != null) // foreach (var poly in obstaclesToIgnore(station)) // l.Add(new DebugCurve(100, 1, "red", poly)); // // // foreach (var obstacle in obstacleTree.GetAllLeaves()) // l.Add(new DebugCurve(50, 0.1, "green", obstacle)); // // l.Add(new DebugCurve(0.1, "blue", new Ellipse(1, 1, station.Position))); // // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(l); // } // // /// // /// edge doesn't cross obstacles // /// NOTE: use method in CdtIntersection insetad! // /// // bool EdgeIsLegal(Station stationA, Station stationB, Point a, Point b) { // var crossings = InteractiveEdgeRouter.IntersectionsOfLineAndRectangleNodeOverPolyline(new LineSegment(a, b), obstacleTree); // Set obstaclesToIgnoreForBundle = ObstaclesToIgnoreForBundle(stationA, stationB); // if (crossings.Count < 0) { // var l = new List(); // var crossingSet = new Set(crossings.Select(ii => ii.Segment1)); // l.AddRange(crossingSet.Select(p=>new DebugCurve(100,1,"red",p))); // l.AddRange(obstaclesToIgnoreForBundle.Select(p=>new DebugCurve(100,0.5,"green",p))); // LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(l); // } // return crossings.All(intersectionInfo => obstaclesToIgnoreForBundle.Contains((Polyline)intersectionInfo.Segment1)); // } // #endif } }