PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/GraphLayout/MSAGL/Routing/Spline/Bundling/Intersections.cs

https://gitlab.com/hoseinyeganloo/automatic-graph-layout
C# | 265 lines | 182 code | 41 blank | 42 comment | 41 complexity | d4f093a097717ea18fcf7ff80477b637 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using Microsoft.Msagl.Core.DataStructures;
  6. using Microsoft.Msagl.Core.Geometry;
  7. using Microsoft.Msagl.Core.Geometry.Curves;
  8. using Microsoft.Msagl.Core.Layout;
  9. using Microsoft.Msagl.Core.Routing;
  10. using Microsoft.Msagl.Routing.Visibility;
  11. using Microsoft.Msagl.DebugHelpers;
  12. namespace Microsoft.Msagl.Routing.Spline.Bundling {
  13. /// <summary>
  14. /// Check intersections between hubs and obstacles with kd-tree
  15. /// </summary>
  16. internal class Intersections {
  17. readonly MetroGraphData metroGraphData;
  18. readonly BundlingSettings bundlingSettings;
  19. Func<Station, Set<Polyline>> obstaclesToIgnore;
  20. /// <summary>
  21. /// represents loose or tight hierarchy
  22. /// </summary>
  23. internal RectangleNode<Polyline> obstacleTree { get; set; }
  24. public Intersections(MetroGraphData metroGraphData, BundlingSettings bundlingSettings,
  25. RectangleNode<Polyline> obstacleTree, Func<Station, Set<Polyline>> obstaclesToIgnore) {
  26. this.metroGraphData = metroGraphData;
  27. this.obstaclesToIgnore = obstaclesToIgnore;
  28. this.bundlingSettings = bundlingSettings;
  29. this.obstacleTree = obstacleTree;
  30. }
  31. internal Set<Polyline> ObstaclesToIgnoreForHub(Station node) {
  32. return (node != null ? obstaclesToIgnore(node) : new Set<Polyline>());
  33. }
  34. internal Set<Polyline> ObstaclesToIgnoreForBundle(Station node, Station adj) {
  35. if (node != null && adj != null)
  36. return obstaclesToIgnore(node) + obstaclesToIgnore(adj);
  37. if (node == null && adj == null)
  38. return new Set<Polyline>();
  39. if (node != null) return obstaclesToIgnore(node);
  40. else return obstaclesToIgnore(adj);
  41. }
  42. #region Intersections with hub
  43. internal bool HubAvoidsObstacles(Station node, Point center, double upperBound,
  44. out List<Tuple<Polyline, Point>> touchedObstacles) {
  45. touchedObstacles = new List<Tuple<Polyline, Point>>();
  46. double minimalDistance = upperBound;
  47. return IntersectCircleWithTree(obstacleTree, center, upperBound, obstaclesToIgnore(node), touchedObstacles, ref minimalDistance);
  48. }
  49. internal bool HubAvoidsObstacles(Point center, double upperBound, Set<Polyline> obstaclesToIgnore) {
  50. List<Tuple<Polyline, Point>> touchedObstacles;
  51. double minimalDistance;
  52. return HubAvoidsObstacles(center, upperBound, obstaclesToIgnore, out touchedObstacles, out minimalDistance);
  53. }
  54. internal double GetMinimalDistanceToObstacles(Station node, Point nodePosition, double upperBound) {
  55. List<Tuple<Polyline, Point>> touchedObstacles = new List<Tuple<Polyline, Point>>();
  56. double minimalDistance = upperBound;
  57. if (!IntersectCircleWithTree(obstacleTree, nodePosition, upperBound, obstaclesToIgnore(node), touchedObstacles, ref minimalDistance))
  58. return 0;
  59. return minimalDistance;
  60. }
  61. bool HubAvoidsObstacles(Point center, double upperBound, Set<Polyline> obstaclesToIgnore,
  62. out List<Tuple<Polyline, Point>> touchedObstacles, out double minimalDistance) {
  63. touchedObstacles = new List<Tuple<Polyline, Point>>();
  64. minimalDistance = upperBound;
  65. return IntersectCircleWithTree(obstacleTree, center, upperBound, obstaclesToIgnore, touchedObstacles, ref minimalDistance);
  66. }
  67. /// <summary>
  68. /// Computes the intersection between the hub and obstacles
  69. /// </summary>
  70. /// <param name="node"></param>
  71. /// <param name="center"></param>
  72. /// <param name="radius"></param>
  73. /// <param name="obstaclesToIgnore">these are the obstacles the are ignored by the method</param>
  74. /// <param name="touchedObstacles">list of pairs (obstacle, closest point on the obstacle)</param>
  75. /// <param name="minimalDistance">min distance from the center to an obstacle</param>
  76. /// <returns>false iff center is inside of an obstacle</returns>
  77. static bool IntersectCircleWithTree(RectangleNode<Polyline> node, Point center, double radius, Set<Polyline> obstaclesToIgnore,
  78. List<Tuple<Polyline, Point>> touchedObstacles, ref double minimalDistance) {
  79. if (!node.Rectangle.Contains(center, radius))
  80. return true;
  81. if (node.UserData == null) {
  82. bool res = IntersectCircleWithTree(node.Left, center, radius, obstaclesToIgnore, touchedObstacles, ref minimalDistance);
  83. if (!res) return false;
  84. res = IntersectCircleWithTree(node.Right, center, radius, obstaclesToIgnore, touchedObstacles, ref minimalDistance);
  85. if (!res) return false;
  86. }
  87. else {
  88. Polyline obstacle = node.UserData;
  89. if (obstaclesToIgnore.Contains(obstacle)) return true;
  90. PointLocation pl = Curve.PointRelativeToCurveLocation(center, obstacle);
  91. if (pl != PointLocation.Outside) return false;
  92. Point touchPoint = obstacle[obstacle.ClosestParameter(center)];
  93. double dist = (touchPoint - center).Length;
  94. if (dist <= radius)
  95. touchedObstacles.Add(new Tuple<Polyline, Point>(obstacle, touchPoint));
  96. minimalDistance = Math.Min(dist, minimalDistance);
  97. }
  98. return true;
  99. }
  100. #endregion
  101. /// <summary>
  102. /// faster way to check segment-polyline intersection
  103. /// NOTE: polyline points should be oriented clockwise
  104. /// </summary>
  105. internal static bool LineSegmentIntersectPolyline(Point start, Point end, Polyline poly) {
  106. Point segDirection = end - start; // the segment direction vector
  107. Debug.Assert(segDirection.Length > ApproximateComparer.DistanceEpsilon);
  108. double tStart = 0; // the maximum entering segment parameter
  109. double tEnd = 1; // the minimum leaving segment parameter
  110. foreach (var p in poly.PolylinePoints) {
  111. //process one edge
  112. var prev = p.PrevOnPolyline;
  113. Point e = prev.Point - p.Point;
  114. double num = Point.CrossProduct(e, start - p.Point);
  115. double den = -Point.CrossProduct(e, segDirection);
  116. if (Math.Abs(den) < ApproximateComparer.Tolerance) { // segment is nearly parallel to this edge
  117. if (num < 0) return false;
  118. continue;
  119. }
  120. //intersection parameter
  121. double t = num / den;
  122. if (den < 0) {
  123. // segment is entering across this edge
  124. tStart = Math.Max(tStart, t);
  125. // segment enters after leaving polygon
  126. if (tStart > tEnd) return false;
  127. }
  128. else {
  129. // segment is leaving across this edge
  130. tEnd = Math.Min(tEnd, t);
  131. // segment leaves before entering polygon
  132. if (tStart > tEnd) return false;
  133. }
  134. }
  135. return true;
  136. }
  137. static internal Polyline Create4gon(Point apex, Point baseCenter, double width1, double width2) {
  138. var norm = (baseCenter - apex).Normalize();
  139. norm = new Point(norm.Y, -norm.X);
  140. return new Polyline(apex + norm * width1 / 2, apex - norm * width1 / 2, baseCenter - norm * width2 / 2, baseCenter + norm * width2 / 2) { Closed = true };
  141. }
  142. #if DEBUG && TEST_MSAGL
  143. /// <summary>
  144. /// check the validness of the drawing:
  145. /// 1. hubs are not inside loose obstacles
  146. /// 2. bundles do not cross loose obstacles
  147. /// </summary>
  148. internal bool HubPositionsAreOK() {
  149. //check polylines
  150. foreach (var line in metroGraphData.Metrolines) {
  151. var poly = line.Polyline;
  152. foreach (var p in poly.PolylinePoints)
  153. Debug.Assert(metroGraphData.PointToStations.ContainsKey(p.Point));
  154. }
  155. foreach (var station in metroGraphData.Stations) {
  156. if (!station.IsRealNode && !HubAvoidsObstacles(station.Position, 0, obstaclesToIgnore(station))) {
  157. if (LayoutAlgorithmSettings.ShowDebugCurvesEnumeration != null) {
  158. HubDebugger.ShowHubs(metroGraphData, bundlingSettings, station);
  159. ShowStationWithObstaclesToIgnore(station, obstacleTree.AllHitItems(station.Position));
  160. }
  161. return false;
  162. }
  163. //bundles
  164. foreach (var adj in station.Neighbors) {
  165. if (ApproximateComparer.Close(adj.Position, station.Position))
  166. return false;
  167. if (!EdgeIsLegal(station, adj, station.Position, adj.Position)) {
  168. if (LayoutAlgorithmSettings.ShowDebugCurvesEnumeration != null) {
  169. //debug visualization
  170. var l = new List<DebugCurve>();
  171. //foreach (var st in metroGraphData.Stations) {
  172. // l.Add(new DebugCurve(100, 0.5, "grey", st.BoundaryCurve));
  173. //}
  174. foreach (var poly in obstaclesToIgnore(station)) {
  175. l.Add(new DebugCurve(100, 5, "green", poly));
  176. }
  177. foreach (var obstacle in obstacleTree.GetAllLeaves()) {
  178. l.Add(new DebugCurve(100, 1, "red", obstacle));
  179. }
  180. l.Add(new DebugCurve(1, "blue", station.BoundaryCurve));
  181. l.Add(new DebugCurve(1, "blue", adj.BoundaryCurve));
  182. l.Add(new DebugCurve(1, "blue", new LineSegment(adj.Position, adj.Neighbors.First().Position)));
  183. l.Add(new DebugCurve(1, "blue", new LineSegment(station.Position, adj.Position)));
  184. LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(l);
  185. //end debug visualization
  186. return false;
  187. }
  188. }
  189. }
  190. }
  191. return true;
  192. }
  193. void ShowStationWithObstaclesToIgnore(Station station, IEnumerable<Polyline> allHitItems) {
  194. var l = new List<DebugCurve>();
  195. foreach (var poly in allHitItems) {
  196. l.Add(new DebugCurve(100, 0.5, "brown", poly));
  197. }
  198. if (obstaclesToIgnore(station) != null)
  199. foreach (var poly in obstaclesToIgnore(station))
  200. l.Add(new DebugCurve(100, 1, "red", poly));
  201. foreach (var obstacle in obstacleTree.GetAllLeaves())
  202. l.Add(new DebugCurve(50, 0.1, "green", obstacle));
  203. l.Add(new DebugCurve(0.1, "blue", new Ellipse(1, 1, station.Position)));
  204. LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(l);
  205. }
  206. /// <summary>
  207. /// edge doesn't cross obstacles
  208. /// NOTE: use method in CdtIntersection insetad!
  209. /// </summary>
  210. bool EdgeIsLegal(Station stationA, Station stationB, Point a, Point b) {
  211. var crossings = InteractiveEdgeRouter.IntersectionsOfLineAndRectangleNodeOverPolyline(new LineSegment(a, b), obstacleTree);
  212. Set<Polyline> obstaclesToIgnoreForBundle = ObstaclesToIgnoreForBundle(stationA, stationB);
  213. if (crossings.Count < 0) {
  214. var l = new List<DebugCurve>();
  215. var crossingSet = new Set<ICurve>(crossings.Select(ii => ii.Segment1));
  216. l.AddRange(crossingSet.Select(p=>new DebugCurve(100,1,"red",p)));
  217. l.AddRange(obstaclesToIgnoreForBundle.Select(p=>new DebugCurve(100,0.5,"green",p)));
  218. LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(l);
  219. }
  220. return crossings.All(intersectionInfo => obstaclesToIgnoreForBundle.Contains((Polyline)intersectionInfo.Segment1));
  221. }
  222. #endif
  223. }
  224. }