Browse Source
Routing algorithm (Graphviz's algorithm is similar): search shortest path (Dijkstra - fast enough, A* would be faster) in the following graph: vertices are corners off all boxes, moved outside of the boxes a little. Edges: any pair of vertices that can be connected by straight line not while not intersecting any box. Having the shortest path as poly-line. Just curve the corners (looks surprisingly good). Edges are routed independently, so they can cross. Having our own implementation, we could work on edge non-crossing algorithm in the future. That is, if the whole layout and routing won't be replaced by GraphSharp in the end. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@5665 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61pull/1/head
21 changed files with 937 additions and 36 deletions
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Windows; |
||||
|
||||
using Debugger.AddIn.Visualizers.Graph.SplineRouting; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.Layout |
||||
{ |
||||
/// <summary>
|
||||
/// Adapts generic <see cref="EdgeRouter" /> to work with <see cref="PositionedGraph" />.
|
||||
/// </summary>
|
||||
public class GraphEdgeRouter |
||||
{ |
||||
EdgeRouter router = new EdgeRouter(); |
||||
|
||||
public GraphEdgeRouter() |
||||
{ |
||||
} |
||||
|
||||
public PositionedGraph RouteEdges(PositionedGraph posGraph) |
||||
{ |
||||
List<RoutedEdge> routedEdges = router.RouteEdges(posGraph.Nodes, posGraph.Edges); |
||||
int i = 0; |
||||
// assume routedEdges come in the same order as posGraph.Edges
|
||||
foreach (var edge in posGraph.Edges) { |
||||
SetEdgeSplinePoints(edge, routedEdges[i]); |
||||
i++; |
||||
} |
||||
return posGraph; |
||||
} |
||||
|
||||
void SetEdgeSplinePoints(PositionedEdge edge, RoutedEdge routedEdge) |
||||
{ |
||||
foreach (Point2D point in routedEdge.SplinePoints) { |
||||
edge.SplinePoints.Add(new Point(point.X, point.Y)); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Windows.Controls; |
||||
using System.Windows.Media; |
||||
using System.Windows.Shapes; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.SplineRouting |
||||
{ |
||||
/// <summary>
|
||||
/// Description of Box.
|
||||
/// </summary>
|
||||
public class Box : IRect, ICloneable |
||||
{ |
||||
public Rectangle rect; |
||||
Box inflatedCache; |
||||
|
||||
public Box(double left, double top, double width, double height) |
||||
{ |
||||
this.rect = new Rectangle(); |
||||
this.Left = left; |
||||
this.Top = top; |
||||
this.Width = width; |
||||
this.Height = height; |
||||
} |
||||
|
||||
public Box(IRect original) |
||||
{ |
||||
this.rect = new Rectangle(); |
||||
this.Left = original.Left; |
||||
this.Top = original.Top; |
||||
this.Width = original.Width; |
||||
this.Height = original.Height; |
||||
} |
||||
|
||||
public double Left { get { return Canvas.GetLeft(rect); } set {Canvas.SetLeft(rect, value); } } |
||||
public double Top { get { return Canvas.GetTop(rect); } set {Canvas.SetTop(rect, value); } } |
||||
public double Width { get { return rect.Width; } set { rect.Width = value; } } |
||||
public double Height { get { return rect.Height; } set { rect.Height = value; } } |
||||
|
||||
public IRect Inflated(double padding) |
||||
{ |
||||
//if (inflatedCache.ContainsKey(padding))
|
||||
if (inflatedCache == null) |
||||
{ |
||||
inflatedCache = GeomUtils.InflateRect(this, padding); |
||||
} |
||||
return inflatedCache; |
||||
} |
||||
|
||||
public object Clone() |
||||
{ |
||||
return new Box(this.Left, this.Top, this.Width, this.Height); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.SplineRouting |
||||
{ |
||||
/// <summary>
|
||||
/// Description of Edge.
|
||||
/// </summary>
|
||||
public class Edge : IEdge |
||||
{ |
||||
public Edge() |
||||
{ |
||||
} |
||||
|
||||
public IRect From { get; set; } |
||||
public IRect To { get; set; } |
||||
public IPoint StartPoint { get; set; } |
||||
public IPoint EndPoint { get; set; } |
||||
} |
||||
} |
@ -0,0 +1,45 @@
@@ -0,0 +1,45 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.SplineRouting |
||||
{ |
||||
/// <summary>
|
||||
/// Description of EdgeRouter.
|
||||
/// </summary>
|
||||
public class EdgeRouter |
||||
{ |
||||
public EdgeRouter() |
||||
{ |
||||
} |
||||
|
||||
public List<RoutedEdge> RouteEdges(IEnumerable<IRect> nodes, IEnumerable<IEdge> edges) |
||||
{ |
||||
var routeGraph = RouteGraph.InitializeVertices(nodes, edges); |
||||
List<RoutedEdge> routedEdges = new List<RoutedEdge>(); |
||||
var occludedEdges = new List<IEdge>(); |
||||
foreach (IEdge edge in edges) { |
||||
var straightEdge = routeGraph.TryRouteEdgeStraight(edge); |
||||
if (straightEdge != null) |
||||
routedEdges.Add(straightEdge); |
||||
else |
||||
occludedEdges.Add(edge); |
||||
} |
||||
if (occludedEdges.Count > 0) { |
||||
// there are some edges that couldn't be routed as straight lines
|
||||
routeGraph.ComputeVisibilityGraph(); |
||||
foreach (IEdge edge in occludedEdges) { |
||||
RoutedEdge routedEdge = routeGraph.RouteEdge(edge); |
||||
routedEdges.Add(routedEdge); |
||||
} |
||||
} |
||||
return routedEdges; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,104 @@
@@ -0,0 +1,104 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Windows; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.SplineRouting |
||||
{ |
||||
/// <summary>
|
||||
/// Description of GeomUtils.
|
||||
/// </summary>
|
||||
public class GeomUtils |
||||
{ |
||||
static readonly double eps = 1e-8; |
||||
|
||||
public GeomUtils() |
||||
{ |
||||
} |
||||
|
||||
public static IPoint RectCenter(IRect rect) |
||||
{ |
||||
return new Point2D(rect.Left + rect.Width / 2, rect.Top + rect.Height / 2); |
||||
} |
||||
|
||||
public static double LineLenght(IPoint lineStart, IPoint lineEnd) |
||||
{ |
||||
var dx = lineEnd.X - lineStart.X; |
||||
var dy = lineEnd.Y - lineStart.Y; |
||||
return Math.Sqrt(dx * dx + dy * dy); |
||||
} |
||||
|
||||
public static Point2D? LineRectIntersection(IPoint lineStart, IPoint lineEnd, IRect rect) |
||||
{ |
||||
double vx = lineEnd.X - lineStart.X; |
||||
double vy = lineEnd.Y - lineStart.Y; |
||||
double right = rect.Left + rect.Width; |
||||
double bottom = rect.Top + rect.Height; |
||||
|
||||
var isectTop = isectHoriz(lineStart, vx, vy, rect.Top, rect.Left, right); |
||||
if (isectTop != null) |
||||
return isectTop; |
||||
var isectBottom = isectHoriz(lineStart, vx, vy, bottom, rect.Left, right); |
||||
if (isectBottom != null) |
||||
return isectBottom; |
||||
var isectLeft = isectVert(lineStart, vx, vy, rect.Left, rect.Top, bottom); |
||||
if (isectLeft != null) |
||||
return isectLeft; |
||||
var isectRight = isectVert(lineStart, vx, vy, right, rect.Top, bottom); |
||||
if (isectRight != null) |
||||
return isectRight; |
||||
|
||||
return null; |
||||
} |
||||
|
||||
static Point2D? isectHoriz(IPoint lineStart, double vx, double vy, double yBound, double left, double right) |
||||
{ |
||||
if (Math.Abs(vy) < eps) |
||||
return null; |
||||
double t = (yBound - lineStart.Y) / vy; |
||||
if (t > 0 && t < 1) |
||||
{ |
||||
double iX = (lineStart.X + t * vx); |
||||
if (iX >= left && iX <= right) |
||||
return new Point2D(iX, yBound); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
static Point2D? isectVert(IPoint lineStart, double vx, double vy, double xBound, double top, double bottom) |
||||
{ |
||||
if (Math.Abs(vx) < eps) |
||||
return null; |
||||
double t = (xBound - lineStart.X) / vx; |
||||
if (t > 0 && t < 1) |
||||
{ |
||||
double iY = (lineStart.Y + t * vy); |
||||
if (iY >= top && iY <= bottom) |
||||
return new Point2D(xBound, iY); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public static Box InflateRect(IRect rect, double padding) |
||||
{ |
||||
return new Box(rect.Left - padding, rect.Top - padding, rect.Width + padding * 2, rect.Height + padding * 2); |
||||
} |
||||
|
||||
public static Point2D Interpolate(Point2D pointA, Point2D pointB, double t) |
||||
{ |
||||
double b = 1 - t; |
||||
return new Point2D(pointA.X * t + pointB.X * b, pointA.Y * t + pointB.Y * b); |
||||
} |
||||
|
||||
/*public static double LineLineIntersection(Point line1Start, Point line1End, Point line2Start, Point line2End) |
||||
{ |
||||
|
||||
}*/ |
||||
} |
||||
} |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.SplineRouting |
||||
{ |
||||
/// <summary>
|
||||
/// Enables passing any type of graph (implementing IRect, IEdge) into EdgeRouter.
|
||||
/// </summary>
|
||||
public interface IEdge |
||||
{ |
||||
IRect From { get; } |
||||
IRect To { get; } |
||||
//IPoint StartPoint { get; set; }
|
||||
//IPoint EndPoint { get; set; }
|
||||
} |
||||
} |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.SplineRouting |
||||
{ |
||||
/// <summary>
|
||||
/// Enables passing any type of graph (implementing IRect, IEdge) into EdgeRouter.
|
||||
/// </summary>
|
||||
public interface IPoint |
||||
{ |
||||
double X { get; set; } |
||||
double Y { get; set; } |
||||
} |
||||
} |
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.SplineRouting |
||||
{ |
||||
/// <summary>
|
||||
/// Enables passing any type of graph (implementing IRect, IEdge) into EdgeRouter.
|
||||
/// </summary>
|
||||
public interface IRect |
||||
{ |
||||
double Left { get; } |
||||
double Top { get; } |
||||
double Width { get; } |
||||
double Height { get; } |
||||
IRect Inflated(double padding); |
||||
} |
||||
} |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.SplineRouting |
||||
{ |
||||
/// <summary>
|
||||
/// Description of Point2D.
|
||||
/// </summary>
|
||||
public struct Point2D : IPoint |
||||
{ |
||||
public Point2D(double x, double y) : this() |
||||
{ |
||||
this.X = x; |
||||
this.Y = y; |
||||
} |
||||
|
||||
public double X { get; set; } |
||||
public double Y { get; set; } |
||||
} |
||||
} |
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.SplineRouting |
||||
{ |
||||
/// <summary>
|
||||
/// Description of EdgeStartEnd.
|
||||
/// </summary>
|
||||
public class EdgeStartEnd |
||||
{ |
||||
public IRect From { get; set; } |
||||
public IRect To { get; set; } |
||||
|
||||
public override bool Equals(object obj) |
||||
{ |
||||
var other = obj as EdgeStartEnd; |
||||
if (other == null) |
||||
return false; |
||||
return this.From == other.From && this.To == other.To; |
||||
} |
||||
|
||||
public override int GetHashCode() |
||||
{ |
||||
return this.From.GetHashCode() ^ this.To.GetHashCode(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,222 @@
@@ -0,0 +1,222 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System.Windows; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.SplineRouting |
||||
{ |
||||
/// <summary>
|
||||
/// Description of RouteGraph.
|
||||
/// </summary>
|
||||
public class RouteGraph |
||||
{ |
||||
static readonly double boxPadding = 15; |
||||
//static readonly double boxSafetyMargin = 5; // inflate boxes for collision testing
|
||||
static readonly double multiEdgeGap = 10; |
||||
|
||||
List<IRect> boxes = new List<IRect>(); |
||||
public List<IRect> Boxes { |
||||
get { return boxes; } |
||||
} |
||||
|
||||
List<RouteVertex> vertices = new List<RouteVertex>(); |
||||
public List<RouteVertex> Vertices { |
||||
get { return vertices; } |
||||
} |
||||
|
||||
AStarShortestPathFinder pathFinder; |
||||
|
||||
public RouteGraph() |
||||
{ |
||||
pathFinder = new AStarShortestPathFinder(this); |
||||
} |
||||
|
||||
public static RouteGraph InitializeVertices(IEnumerable<IRect> nodes, IEnumerable<IEdge> edges) |
||||
{ |
||||
var graph = new RouteGraph(); |
||||
// add vertices for node corners
|
||||
foreach (var node in nodes) { |
||||
graph.Boxes.Add(node); |
||||
foreach (var vertex in GetRectCorners(node, boxPadding)) { |
||||
graph.Vertices.Add(vertex); |
||||
} |
||||
} |
||||
// add vertices for egde endpoints
|
||||
foreach (var multiEdgeGroup in edges.GroupBy(edge => GetStartEnd(edge))) { |
||||
int multiEdgeCount = multiEdgeGroup.Count(); |
||||
IRect fromRect = multiEdgeGroup.First().From; |
||||
IRect toRect = multiEdgeGroup.First().To; |
||||
var sourceCenter = GeomUtils.RectCenter(fromRect); |
||||
var targetCenter = GeomUtils.RectCenter(toRect); |
||||
if (Math.Abs(sourceCenter.X - targetCenter.X) > Math.Abs(sourceCenter.Y - targetCenter.Y) || |
||||
(fromRect == toRect)) |
||||
{ |
||||
// the line is horizontal
|
||||
double multiEdgeSpanSource = GetMultiEdgeSpan(fromRect.Height, multiEdgeCount, multiEdgeGap); |
||||
double multiEdgeSpanTarget = GetMultiEdgeSpan(toRect.Height, multiEdgeCount, multiEdgeGap); |
||||
double originSourceCurrentY = sourceCenter.Y - multiEdgeSpanSource / 2; |
||||
double originTargetCurrentY = targetCenter.Y - multiEdgeSpanTarget / 2; |
||||
foreach (var edge in multiEdgeGroup) { |
||||
Point2D sourceOrigin = new Point2D(sourceCenter.X, originSourceCurrentY); |
||||
Point2D targetOrigin = new Point2D(targetCenter.X, originTargetCurrentY); |
||||
// Here user could provide custom edgeStart and edgeEnd
|
||||
// inflate boxes a little so that edgeStart and edgeEnd are a little outside of the box (to prevent floating point errors)
|
||||
if (edge.From == edge.To) { |
||||
var edgeStart = new Point2D(fromRect.Left + fromRect.Width + 0.01, originSourceCurrentY); |
||||
var edgeEnd = new Point2D(fromRect.Left + fromRect.Width / 2, fromRect.Top); |
||||
graph.AddEdgeEndpointVertices(edge, edgeStart, edgeEnd); |
||||
} else { |
||||
var edgeStart = GeomUtils.LineRectIntersection(sourceOrigin, targetOrigin, edge.From.Inflated(1e-3)); |
||||
var edgeEnd = GeomUtils.LineRectIntersection(sourceOrigin, targetOrigin, edge.To.Inflated(1e-3)); |
||||
graph.AddEdgeEndpointVertices(edge, edgeStart, edgeEnd); |
||||
} |
||||
originSourceCurrentY += multiEdgeSpanSource / (multiEdgeCount - 1); |
||||
originTargetCurrentY += multiEdgeSpanTarget / (multiEdgeCount - 1); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
// the line is vertical
|
||||
double multiEdgeSpanSource = GetMultiEdgeSpan(fromRect.Width, multiEdgeCount, multiEdgeGap); |
||||
double multiEdgeSpanTarget = GetMultiEdgeSpan(toRect.Width, multiEdgeCount, multiEdgeGap); |
||||
double originSourceCurrentX = sourceCenter.X - multiEdgeSpanSource / 2; |
||||
double originTargetCurrentX = targetCenter.X - multiEdgeSpanTarget / 2; |
||||
foreach (var edge in multiEdgeGroup) { |
||||
Point2D sourceOrigin = new Point2D(originSourceCurrentX, sourceCenter.Y); |
||||
Point2D targetOrigin = new Point2D(originTargetCurrentX, targetCenter.Y); |
||||
// Here user could provide custom edgeStart and edgeEnd
|
||||
// inflate boxes a little so that edgeStart and edgeEnd are a little outside of the box (to prevent floating point errors)
|
||||
var edgeStart = GeomUtils.LineRectIntersection(sourceOrigin, targetOrigin, edge.From.Inflated(1e-3)); |
||||
var edgeEnd = GeomUtils.LineRectIntersection(sourceOrigin, targetOrigin, edge.To.Inflated(1e-3)); |
||||
graph.AddEdgeEndpointVertices(edge, edgeStart, edgeEnd); |
||||
originSourceCurrentX += multiEdgeSpanSource / (multiEdgeCount - 1); |
||||
originTargetCurrentX += multiEdgeSpanTarget / (multiEdgeCount - 1); |
||||
} |
||||
} |
||||
} |
||||
return graph; |
||||
} |
||||
|
||||
void AddEdgeEndpointVertices(IEdge edge, Point2D? edgeStart, Point2D? edgeEnd) |
||||
{ |
||||
if (edgeStart == null || edgeEnd == null) { |
||||
// should not happen
|
||||
return; |
||||
} |
||||
var startPoint = new RouteVertex(edgeStart.Value.X, edgeStart.Value.Y); |
||||
startPoint.IsEdgeEndpoint = true; |
||||
var endPoint = new RouteVertex(edgeEnd.Value.X, edgeEnd.Value.Y); |
||||
endPoint.IsEdgeEndpoint = true; |
||||
this.vertices.Add(startPoint); |
||||
this.vertices.Add(endPoint); |
||||
// remember what RouteVertices we created for this user edge
|
||||
this.setStartVertex(edge, startPoint); |
||||
this.setEndVertex(edge, endPoint); |
||||
} |
||||
|
||||
static IEnumerable<RouteVertex> GetRectCorners(IRect rect, double padding) |
||||
{ |
||||
double left = rect.Left - padding; |
||||
double top = rect.Top - padding; |
||||
double right = left + rect.Width + 2 * padding; |
||||
double bottom = top + rect.Height + 2 * padding; |
||||
yield return new RouteVertex(left, top); |
||||
yield return new RouteVertex(right, top); |
||||
yield return new RouteVertex(right, bottom); |
||||
yield return new RouteVertex(left, bottom); |
||||
} |
||||
|
||||
public void ComputeVisibilityGraph() |
||||
{ |
||||
for (int i = 0; i < this.Vertices.Count; i++) { |
||||
for (int j = i + 1; j < this.Vertices.Count; j++) { |
||||
var vertex = this.Vertices[i]; |
||||
var vertex2 = this.Vertices[j]; |
||||
if (Visible(vertex, vertex2)) |
||||
{ |
||||
// bidirectional edge
|
||||
vertex.AddNeighbor(vertex2); |
||||
vertex2.AddNeighbor(vertex); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
public bool Visible(IPoint vertex, IPoint vertex2) |
||||
{ |
||||
// test for intersection with every box
|
||||
foreach (var rect in this.Boxes) { |
||||
if (GeomUtils.LineRectIntersection(vertex, vertex2, rect) != null) |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
public RoutedEdge RouteEdge(IEdge edge) |
||||
{ |
||||
var pathVertices = pathFinder.FindShortestPath(getStartVertex(edge), getEndVertex(edge)); |
||||
return BuildRoutedEdge(pathVertices); |
||||
} |
||||
|
||||
public RoutedEdge TryRouteEdgeStraight(IEdge edge) |
||||
{ |
||||
var startVertex = getStartVertex(edge); |
||||
var endVertex = getEndVertex(edge); |
||||
if (Visible(startVertex, endVertex)) { |
||||
// route the edge straight
|
||||
return BuildRoutedEdge(new [] {startVertex, endVertex }); |
||||
} else |
||||
return null; |
||||
} |
||||
|
||||
public RoutedEdge BuildRoutedEdge(IEnumerable<IPoint> points) |
||||
{ |
||||
var routedEdge = new RoutedEdge(); |
||||
foreach (var point in points) { |
||||
routedEdge.Points.Add(new Point2D(point.X, point.Y)); |
||||
} |
||||
return routedEdge; |
||||
} |
||||
|
||||
Dictionary<IEdge, RouteVertex> edgeStarts = new Dictionary<IEdge, RouteVertex>(); |
||||
Dictionary<IEdge, RouteVertex> edgeEnds = new Dictionary<IEdge, RouteVertex>(); |
||||
|
||||
RouteVertex getStartVertex(IEdge edge) |
||||
{ |
||||
return edgeStarts[edge]; |
||||
} |
||||
RouteVertex getEndVertex(IEdge edge) |
||||
{ |
||||
return edgeEnds[edge]; |
||||
} |
||||
void setStartVertex(IEdge edge, RouteVertex value) |
||||
{ |
||||
edgeStarts[edge] = value; |
||||
} |
||||
void setEndVertex(IEdge edge, RouteVertex value) |
||||
{ |
||||
edgeEnds[edge] = value; |
||||
} |
||||
|
||||
static EdgeStartEnd GetStartEnd(IEdge edge) |
||||
{ |
||||
return new EdgeStartEnd { From = edge.From, To = edge.To }; |
||||
} |
||||
|
||||
static double GetMultiEdgeSpan(double space, int multiEdgeCount, double multiEdgeGap) |
||||
{ |
||||
if ((multiEdgeCount + 1) * multiEdgeGap < space) |
||||
// the edges fit, maintain the gap
|
||||
return (multiEdgeCount - 1) * multiEdgeGap; |
||||
else |
||||
// there are too many edges, we have to make smaller gaps to fit edges into given space
|
||||
return space - multiEdgeGap; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.SplineRouting |
||||
{ |
||||
/// <summary>
|
||||
/// Edge in the RouteGraph.
|
||||
/// </summary>
|
||||
public class RouteGraphEdge |
||||
{ |
||||
public RouteVertex StartVertex { get; private set; } |
||||
public RouteVertex EndVertex { get; private set; } |
||||
|
||||
public double Length { get; private set; } |
||||
|
||||
public RouteGraphEdge(RouteVertex startVertex, RouteVertex endVertex) |
||||
{ |
||||
this.StartVertex = startVertex; |
||||
this.EndVertex = endVertex; |
||||
this.Length = GeomUtils.LineLenght(startVertex, endVertex); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.SplineRouting |
||||
{ |
||||
/// <summary>
|
||||
/// Description of RouteVertex.
|
||||
/// </summary>
|
||||
public class RouteVertex : IPoint |
||||
{ |
||||
public double X { get; set; } |
||||
public double Y { get; set; } |
||||
|
||||
public RouteVertex(double x, double y) |
||||
{ |
||||
this.X = x; |
||||
this.Y = y; |
||||
} |
||||
|
||||
List<RouteGraphEdge> neighbors = new List<RouteGraphEdge>(); |
||||
public List<RouteGraphEdge> Neighbors { |
||||
get { return neighbors; } |
||||
} |
||||
|
||||
public RouteVertex() |
||||
{ |
||||
Reset(); |
||||
IsUsed = false; |
||||
IsEdgeEndpoint = false; |
||||
} |
||||
|
||||
public void AddNeighbor(RouteVertex target) |
||||
{ |
||||
this.Neighbors.Add(new RouteGraphEdge(this, target)); |
||||
} |
||||
|
||||
public double Distance { get; set; } |
||||
public bool IsPermanent { get; set; } |
||||
public RouteVertex Predecessor { get; set; } |
||||
public bool IsEdgeEndpoint { get; set; } |
||||
public bool IsUsed { get; set; } |
||||
public bool IsAvailable { get { return !IsUsed && !IsEdgeEndpoint; } } |
||||
|
||||
public void Reset() |
||||
{ |
||||
Distance = 10e+6; |
||||
IsPermanent = false; |
||||
Predecessor = null; |
||||
//IsUsed = false;
|
||||
//IsEdgeEndpoint = false;
|
||||
} |
||||
} |
||||
} |
@ -0,0 +1,87 @@
@@ -0,0 +1,87 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Collections.ObjectModel; |
||||
using System.Linq; |
||||
using System.Windows; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.SplineRouting |
||||
{ |
||||
/// <summary>
|
||||
/// The result of the main method EdgeRouter.RouteEdges(), passed back to the user.
|
||||
/// </summary>
|
||||
public class RoutedEdge |
||||
{ |
||||
public double Lenght { |
||||
get { |
||||
double len = 0; |
||||
for (int i = 1; i < this.Points.Count; i++) { |
||||
len += GeomUtils.LineLenght(this.Points[i - 1], this.Points[i]); |
||||
} |
||||
return len; |
||||
} |
||||
} |
||||
|
||||
List<Point2D> points; |
||||
public List<Point2D> Points |
||||
{ |
||||
get { return this.points; } |
||||
} |
||||
|
||||
public RoutedEdge() |
||||
{ |
||||
this.points = new List<Point2D>(); |
||||
} |
||||
|
||||
ReadOnlyCollection<Point2D> splinePoints; |
||||
public ReadOnlyCollection<Point2D> SplinePoints |
||||
{ |
||||
get |
||||
{ |
||||
if (this.splinePoints == null) |
||||
{ |
||||
List<Point2D> sPoints = RouteSpline(this.Points); |
||||
splinePoints = sPoints.AsReadOnly(); |
||||
} |
||||
return this.splinePoints; |
||||
} |
||||
} |
||||
|
||||
private static double tBend = 0.4; |
||||
|
||||
private List<Point2D> RouteSpline(List<Point2D> anchorPoints) |
||||
{ |
||||
var result = new List<Point2D>(); |
||||
if (anchorPoints.Count == 0) |
||||
return new List<Point2D>(); |
||||
Point2D point1 = anchorPoints[0]; |
||||
result.Add(point1); |
||||
for (int i = 2; i < anchorPoints.Count; i++) { |
||||
var point2 = anchorPoints[i - 1]; |
||||
var point3 = anchorPoints[i]; |
||||
var anchor1 = GeomUtils.Interpolate(point1, point2, 1 - tBend); |
||||
var anchor2 = GeomUtils.Interpolate(point2, point3, tBend); |
||||
// straight segment
|
||||
result.Add(anchor1); // guide point1 -> anchor1
|
||||
result.Add(point1); |
||||
result.Add(anchor1); // guide anchor1 -> point2
|
||||
// bend
|
||||
result.Add(point2); // guide anchor1 -> point2 (more carved ?)
|
||||
result.Add(point2); |
||||
result.Add(anchor2); // guide point2 -> anchor2
|
||||
point1 = anchor2; |
||||
} |
||||
// last straight segment
|
||||
var lastPoint = anchorPoints[anchorPoints.Count - 1]; |
||||
result.Add(lastPoint); |
||||
result.Add(point1); |
||||
result.Add(lastPoint); |
||||
return result; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
// <file>
|
||||
// <copyright see="prj:///doc/copyright.txt"/>
|
||||
// <license see="prj:///doc/license.txt"/>
|
||||
// <owner name="Martin Koníček" email="martin.konicek@gmail.com"/>
|
||||
// <version>$Revision$</version>
|
||||
// </file>
|
||||
using System.Collections.Generic; |
||||
using System.Linq; |
||||
using System; |
||||
|
||||
namespace Debugger.AddIn.Visualizers.Graph.SplineRouting |
||||
{ |
||||
/// <summary>
|
||||
/// Description of AStarShortestPathFinder.
|
||||
/// </summary>
|
||||
public class AStarShortestPathFinder |
||||
{ |
||||
RouteGraph graph; |
||||
|
||||
public AStarShortestPathFinder(RouteGraph routeGraph) |
||||
{ |
||||
this.graph = routeGraph; |
||||
} |
||||
|
||||
public List<RouteVertex> FindShortestPath(RouteVertex start, RouteVertex end) |
||||
{ |
||||
start.IsEdgeEndpoint = false; |
||||
end.IsEdgeEndpoint = false; |
||||
foreach (var vertex in this.graph.Vertices) { |
||||
vertex.Reset(); |
||||
} |
||||
start.Distance = 0; |
||||
bool reached = false; |
||||
while (!reached) |
||||
{ |
||||
RouteVertex minVertex = null; |
||||
foreach (var minCandidate in graph.Vertices.Where(v => !v.IsPermanent && v.IsAvailable)) { |
||||
if (minVertex == null || minCandidate.Distance < minVertex.Distance) { |
||||
minVertex = minCandidate; |
||||
} |
||||
} |
||||
minVertex.IsPermanent = true; |
||||
if (minVertex == end) { |
||||
reached = true; |
||||
} |
||||
foreach (var edge in minVertex.Neighbors) { |
||||
if (minVertex.Distance + edge.Length < edge.EndVertex.Distance) { |
||||
edge.EndVertex.Distance = minVertex.Distance + edge.Length; |
||||
edge.EndVertex.Predecessor = minVertex; |
||||
} |
||||
} |
||||
} |
||||
List<RouteVertex> path = new List<RouteVertex>(); |
||||
RouteVertex pathVertex = end; |
||||
while (pathVertex != start) { |
||||
if (pathVertex == null) |
||||
break; |
||||
path.Add(pathVertex); |
||||
//pathVertex.IsUsed = true;
|
||||
pathVertex = pathVertex.Predecessor; |
||||
} |
||||
path.Add(start); |
||||
start.IsEdgeEndpoint = true; |
||||
end.IsEdgeEndpoint = true; |
||||
path.Reverse(); |
||||
return path; |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue