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;
}
}
}