Aetheria/Assets/Plugins/MSAGL/Routing/Spline/Bundling/NodePositionsAdjuster.cs
Reinier Vens dbc522a224 Weapos Rejiggered
Modified weapon scripts to eliminate specialized projectile behavior - allows charged weapons to have projectiles and modify weapon stats when charged
Added machine gun weapon & VFX
Moved third party code to Plugins folder to improve compile times
2021-01-31 03:52:32 +00:00

753 lines
34 KiB
C#

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.Routing;
using Microsoft.Msagl.Routing.ConstrainedDelaunayTriangulation;
using Microsoft.Msagl.DebugHelpers;
namespace Microsoft.Msagl.Routing.Spline.Bundling {
/// <summary>
/// Adjust current bundle-routing with a number of heuristic
/// </summary>
public class NodePositionsAdjuster {
/// <summary>
/// Algorithm settings
/// </summary>
readonly BundlingSettings bundlingSettings;
/// <summary>
/// bundle data
/// </summary>
readonly MetroGraphData metroGraphData;
NodePositionsAdjuster(MetroGraphData metroGraphData, BundlingSettings bundlingSettings) {
this.metroGraphData = metroGraphData;
this.bundlingSettings = bundlingSettings;
}
/// <summary>
/// apply a number of heuristics to improve current routing
/// </summary>
internal static void FixRouting(MetroGraphData metroGraphData, BundlingSettings bundlingSettings) {
// #if DEBUG && TEST_MSAGL
// Debug.Assert(metroGraphData.looseIntersections.HubPositionsAreOK());
// #endif
//TimeMeasurer.DebugOutput("Initial cost = " + CostCalculator.Cost(metroGraphData, bundlingSettings));
//TimeMeasurer.DebugOutput("Initial cost of forces: " + CostCalculator.CostOfForces(metroGraphData, bundlingSettings));
var adjuster = new NodePositionsAdjuster(metroGraphData, bundlingSettings);
adjuster.GlueConflictingNodes();
adjuster.UnglueEdgesFromBundleToSaveInk(true);
var step = 0;
int MaxSteps = 10;
while (++step < MaxSteps) {
/*#if DEBUG && TEST_MSAGL
Debug.Assert(metroGraphData.looseIntersections.HubPositionsAreOK());
#endif*/
//heuristics to improve routing
bool progress = adjuster.GlueConflictingNodes();
progress |= adjuster.RelaxConstrainedEdges();
progress |= (step <= 3 && adjuster.UnglueEdgesFromBundleToSaveInk(false));
progress |= adjuster.GlueCollinearNeighbors(step);
progress |= (step == 3 && adjuster.RemoveDoublePathCrossings());
if (!progress) break;
}
//one SA has to be executed with bundle forces
metroGraphData.cdtIntersections.ComputeForcesForBundles = true;
adjuster.RemoveDoublePathCrossings();
adjuster.UnglueEdgesFromBundleToSaveInk(true);
while (adjuster.GlueConflictingNodes()) { }
metroGraphData.Initialize(true); //this time initialize the tight enterables also
// HubDebugger.ShowHubs(metroGraphData, bundlingSettings);
//TimeMeasurer.DebugOutput("NodePositionsAdjuster stopped after " + step + " steps");
//HubDebugger.ShowHubs(metroGraphData, bundlingSettings, true);
//TimeMeasurer.DebugOutput("Final cost: " + CostCalculator.Cost(metroGraphData, bundlingSettings));
//TimeMeasurer.DebugOutput("Final cost of forces: " + CostCalculator.CostOfForces(metroGraphData, bundlingSettings));
}
#region Gluing coinciding nodes
/// <summary>
/// unite the nodes that are close to each other
/// </summary>
bool GlueConflictingNodes() {
var circlesHierarchy = GetCirclesHierarchy();
if (circlesHierarchy == null) return false;
var gluingMap = new Dictionary<Station, Station>();
var gluedDomain = new Set<Station>();
RectangleNodeUtils.CrossRectangleNodes<Station>(circlesHierarchy, circlesHierarchy,
(i, j) => TryToGlueNodes(i, j, gluingMap, gluedDomain));
if (gluingMap.Count == 0)
return false;
for (int i = 0; i < metroGraphData.Edges.Length; i++)
RegenerateEdge(gluingMap, i);
//can it be more efficient?
HashSet<Point> affectedPoints = new HashSet<Point>();
foreach (var s in gluedDomain) {
affectedPoints.Add(s.Position);
foreach (var neig in s.Neighbors)
if (!neig.IsRealNode)
affectedPoints.Add(neig.Position);
}
//TimeMeasurer.DebugOutput("gluing nodes");
metroGraphData.Initialize(false);
SimulatedAnnealing.FixRouting(metroGraphData, bundlingSettings, affectedPoints);
return true;
}
RectangleNode<Station> GetCirclesHierarchy() {
foreach (var v in metroGraphData.VirtualNodes()) {
v.Radius = GetCurrentHubRadius(v);
}
return RectangleNode<Station>.CreateRectangleNodeOnEnumeration(from i in metroGraphData.VirtualNodes()
let p = i.Position
let r = Math.Max(i.Radius, 5)
let del = new Point(r, r)
let b = new Rectangle(p + del, p - del)
select new RectangleNode<Station>(i, b));
}
double GetCurrentHubRadius(Station node) {
if (node.IsRealNode) {
return node.BoundaryCurve.BoundingBox.Diagonal/2;
}
else {
double idealR = node.cachedIdealRadius;
//TODO: which one?
double r = metroGraphData.looseIntersections.GetMinimalDistanceToObstacles(node, node.Position, idealR);
//double r = idealR;
Debug.Assert(r <= idealR);
foreach (var adj in node.Neighbors)
r = Math.Min(r, (node.Position - adj.Position).Length);
return r;
}
}
void TryToGlueNodes(Station i, Station j, Dictionary<Station, Station> gluingMap, Set<Station> gluedDomain) {
Debug.Assert(i != j);
double d = (i.Position - j.Position).Length;
double r1 = Math.Max(i.Radius, 5);
double r2 = Math.Max(j.Radius, 5);
if (d >= r1 + r2)
return;
//we are greedily trying to glue i to j
if (!TryGlueOrdered(i, j, gluedDomain, gluingMap))
TryGlueOrdered(j, i, gluedDomain, gluingMap);
}
bool TryGlueOrdered(Station i, Station j, Set<Station> gluedDomain, Dictionary<Station, Station> gluingMap) {
if (!gluingMap.ContainsKey(i) && !gluedDomain.Contains(i) && NodeGluingIsAllowed(i, j, gluingMap)) {
Map(i, j, gluedDomain, gluingMap);
//TimeMeasurer.DebugOutput("gluing nodes " + i.serialNumber + " and " + j.serialNumber);
return true;
}
return false;
}
void Map(Station i, Station j, Set<Station> gluedDomain, Dictionary<Station, Station> gluingMap) {
gluingMap[i] = j;
gluedDomain.Insert(j);
}
/// <summary>
/// trying to glue i to j
/// </summary>
bool NodeGluingIsAllowed(Station i, Station j, Dictionary<Station, Station> gluingMap) {
foreach (var adj in i.Neighbors) {
var k = Glued(adj, gluingMap);
//1. check that we can merge these stations (== no intersections)
Set<Polyline> obstaclesToIgnore = metroGraphData.looseIntersections.ObstaclesToIgnoreForBundle(k, i);
if (!metroGraphData.cdtIntersections.EdgeIsLegal(k, j, k.Position, j.Position, obstaclesToIgnore))
return false;
}
//2. check that cost of the routing is reduced
double delta = ComputeCostDeltaAfterNodeGluing(i, j, gluingMap);
if (delta < 0) return false;
return true;
}
double ComputeCostDeltaAfterNodeGluing(Station i, Station j, Dictionary<Station, Station> gluingMap) {
double d = (i.Position - j.Position).Length;
if (i.Radius >= d || j.Radius >= d) return 1.0;
double gain = 0;
//ink
double oldInk = metroGraphData.Ink;
double newInk = metroGraphData.Ink - (j.Position - i.Position).Length;
foreach (var adj in i.Neighbors) {
var k = Glued(adj, gluingMap);
newInk -= (k.Position - i.Position).Length;
newInk += (metroGraphData.RealEdgeCount(k, j) == 0 ? (k.Position - j.Position).Length : 0);
}
gain += CostCalculator.InkError(oldInk, newInk, bundlingSettings);
//path lengths
foreach (var metroInfo in metroGraphData.MetroNodeInfosOfNode(i)) {
double oldLength = metroInfo.Metroline.Length;
double newLength = metroInfo.Metroline.Length;
PolylinePoint pi = metroInfo.PolyPoint;
PolylinePoint pa = pi.Prev;
PolylinePoint pb = pi.Next;
newLength -= (pa.Point - i.Position).Length + (pb.Point - i.Position).Length;
newLength += (pa.Point - j.Position).Length + (pb.Point - j.Position).Length;
gain += CostCalculator.PathLengthsError(oldLength, newLength, metroInfo.Metroline.IdealLength,
bundlingSettings);
}
return gain;
}
void RegenerateEdge(Dictionary<Station, Station> gluingMap, int edgeIndex) {
var poly = metroGraphData.Metrolines[edgeIndex].Polyline;
if (!poly.Any(p => gluingMap.ContainsKey(metroGraphData.PointToStations[p])))
return;
metroGraphData.Edges[edgeIndex].Curve =
new Polyline(GluedPolyline(poly.Select(p => metroGraphData.PointToStations[p]).ToArray(), gluingMap));
}
static IEnumerable<Point> GluedPolyline(Station[] metroline, Dictionary<Station, Station> gluedMap) {
int i;
var ret = new Stack<Station>();
ret.Push(metroline[0]);
var seenStations = new Set<Station>();
for (i = 1; i < metroline.Length - 1; i++) {
var station = Glued(metroline[i], gluedMap);
if (seenStations.Contains(station)) {
//we made a cycle - need to cut it out
while (ret.Peek() != station)
seenStations.Delete(ret.Pop());
continue;
}
if (ApproximateComparer.Close(station.Position, ret.Peek().Position))
continue;
seenStations.Insert(station);
ret.Push(station);
}
ret.Push(metroline[i]);
return ret.Reverse().Select(n => n.Position);
}
static Station Glued(Station i, Dictionary<Station, Station> gluedMap) {
Station j;
return gluedMap.TryGetValue(i, out j) ? j : i;
}
#endregion
#region Shortcut single polylines
double ink;
Dictionary<Metroline, double> polylineLength;
/// <summary>
/// Unbundle unnecessary edges:
/// instead of one bundle (a->bcd) we get two bundles (a->b,a->cd) with smaller ink
/// </summary>
bool UnglueEdgesFromBundleToSaveInk(bool alwaysExecuteSA) {
var segsToPolylines = new Dictionary<PointPair, Set<Metroline>>();
ink = metroGraphData.Ink;
polylineLength = new Dictionary<Metroline, double>();
//create polylines
foreach (var metroline in metroGraphData.Metrolines) {
polylineLength[metroline] = metroline.Length;
for (var pp = metroline.Polyline.StartPoint; pp.Next != null; pp = pp.Next) {
var segment = new PointPair(pp.Point, pp.Next.Point);
CollectionUtilities.AddToMap(segsToPolylines, segment, metroline);
}
}
var affectedPoints = new HashSet<Point>();
var progress = false;
foreach (var metroline in metroGraphData.Metrolines) {
var obstaclesAllowedToIntersect =
metroGraphData.PointToStations[metroline.Polyline.Start].EnterableLoosePolylines*
metroGraphData.PointToStations[metroline.Polyline.End].EnterableLoosePolylines;
if (TrySeparateOnPolyline(metroline, segsToPolylines, affectedPoints, obstaclesAllowedToIntersect))
progress = true;
}
if (progress) //TimeMeasurer.DebugOutput("unbundling");
metroGraphData.Initialize(false);
if (alwaysExecuteSA || progress)
SimulatedAnnealing.FixRouting(metroGraphData, bundlingSettings, alwaysExecuteSA ? null : affectedPoints);
return progress;
}
bool TrySeparateOnPolyline(Metroline metroline, Dictionary<PointPair, Set<Metroline>> segsToPolylines,
HashSet<Point> affectedPoints, Set<Polyline> obstaclesAllowedToIntersect) {
bool progress = false;
var relaxing = true;
while (relaxing) {
relaxing = false;
for (var p = metroline.Polyline.StartPoint; p.Next != null && p.Next.Next != null; p = p.Next)
if (TryShortcutPolypoint(p, segsToPolylines, affectedPoints, obstaclesAllowedToIntersect))
relaxing = true;
if (relaxing) progress = true;
}
return progress;
}
bool TryShortcutPolypoint(PolylinePoint pp, Dictionary<PointPair, Set<Metroline>> segsToPolylines, HashSet<Point> affectedPoints, Set<Polyline> obstaclesAllowedToIntersect) {
if (SeparationShortcutAllowed(pp, segsToPolylines, obstaclesAllowedToIntersect)) {
affectedPoints.Add(pp.Point);
affectedPoints.Add(pp.Next.Point);
affectedPoints.Add(pp.Next.Next.Point);
RemoveShortcuttedPolypoint(pp, segsToPolylines);
return true;
}
return false;
}
/// <summary>
/// allowed iff line (a,c) is legal and inkgain > 0
/// </summary>
bool SeparationShortcutAllowed(PolylinePoint pp, Dictionary<PointPair, Set<Metroline>> segsToPolylines, Set<Polyline> obstaclesAllowedToIntersect) {
var a = pp.Point;
var b = pp.Next.Point;
var c = pp.Next.Next.Point;
var aStation = metroGraphData.PointToStations[a];
var bStation = metroGraphData.PointToStations[b];
var cStation = metroGraphData.PointToStations[c];
//1. intersections
if (!metroGraphData.cdtIntersections.EdgeIsLegal(aStation, cStation,
a, c,
obstaclesAllowedToIntersect*
bStation.EnterableLoosePolylines*
(aStation.EnterableLoosePolylines +
cStation.EnterableLoosePolylines)))
return false;
//2. cost gain
var inkgain = GetInkgain(pp, segsToPolylines, a, b, c);
if (inkgain < 0) return false;
return true;
}
double GetInkgain(PolylinePoint pp, Dictionary<PointPair, Set<Metroline>> segsToPolylines, Point a, Point b, Point c) {
Set<Metroline> abPolylines, bcPolylines, abcPolylines;
FindPolylines(pp, segsToPolylines, out abPolylines, out bcPolylines, out abcPolylines);
double gain = 0;
//ink
double oldInk = ink;
double newInk = ink;
double ab = (a - b).Length;
double bc = (b - c).Length;
double ac = (a - c).Length;
if (abPolylines.Count == abcPolylines.Count) newInk -= ab;
if (bcPolylines.Count == abcPolylines.Count) newInk -= bc;
if (!segsToPolylines.ContainsKey(new PointPair(a, c)) || segsToPolylines[new PointPair(a, c)].Count == 0)
newInk += ac;
gain += CostCalculator.InkError(oldInk, newInk, bundlingSettings);
//path lengths
foreach (var metroline in abcPolylines) {
double oldLength = polylineLength[metroline];
double newLength = polylineLength[metroline];
newLength -= ab + bc - ac;
gain += CostCalculator.PathLengthsError(oldLength, newLength, metroline.IdealLength, bundlingSettings);
}
//radii
double nowR = GetCurrentHubRadius(metroGraphData.PointToStations[a]);
double widthABC = metroGraphData.GetWidth(abcPolylines, bundlingSettings.EdgeSeparation);
double widthABD = metroGraphData.GetWidth(abPolylines - abcPolylines, bundlingSettings.EdgeSeparation);
double idealR = HubRadiiCalculator.GetMinRadiusForTwoAdjacentBundles(nowR, a, c, b, widthABC, widthABD, metroGraphData, bundlingSettings);
if (idealR > nowR) {
gain -= CostCalculator.RError(idealR, nowR, bundlingSettings);
}
//check opposite side
nowR = GetCurrentHubRadius(metroGraphData.PointToStations[c]);
double widthCBD = metroGraphData.GetWidth(bcPolylines - abcPolylines, bundlingSettings.EdgeSeparation);
idealR = HubRadiiCalculator.GetMinRadiusForTwoAdjacentBundles(nowR, c, b, a, widthCBD, widthABC, metroGraphData, bundlingSettings);
if (idealR > nowR) {
gain -= CostCalculator.RError(idealR, nowR, bundlingSettings);
}
return gain;
}
void RemoveShortcuttedPolypoint(PolylinePoint pp, Dictionary<PointPair, Set<Metroline>> segsToPolylines) {
var a = pp.Point;
var b = pp.Next.Point;
var c = pp.Next.Next.Point;
Set<Metroline> abPolylines, bcPolylines, abcPolylines;
FindPolylines(pp, segsToPolylines, out abPolylines, out bcPolylines, out abcPolylines);
double ab = (a - b).Length;
double bc = (b - c).Length;
double ac = (a - c).Length;
//fixing ink
if (abPolylines.Count == abcPolylines.Count) ink -= ab;
if (bcPolylines.Count == abcPolylines.Count) ink -= bc;
if (!segsToPolylines.ContainsKey(new PointPair(a, c)) || segsToPolylines[new PointPair(a, c)].Count == 0)
ink += ac;
//fixing edge lengths
foreach (var metroline in abcPolylines)
polylineLength[metroline] -= ab + bc - ac;
//fixing polylines
foreach (var metroline in abcPolylines) {
RemovePolypoint(metroline.Polyline.PolylinePoints.First(p => p.Point == b));
CollectionUtilities.RemoveFromMap(segsToPolylines, new PointPair(a, b), metroline);
CollectionUtilities.RemoveFromMap(segsToPolylines, new PointPair(b, c), metroline);
CollectionUtilities.AddToMap(segsToPolylines, new PointPair(a, c), metroline);
}
}
void FindPolylines(PolylinePoint pp, Dictionary<PointPair, Set<Metroline>> segsToPolylines,
out Set<Metroline> abPolylines, out Set<Metroline> bcPolylines, out Set<Metroline> abcPolylines) {
Point a = pp.Point;
Point b = pp.Next.Point;
Point c = pp.Next.Next.Point;
abPolylines = segsToPolylines[new PointPair(a, b)];
bcPolylines = segsToPolylines[new PointPair(b, c)];
abcPolylines = abPolylines * bcPolylines;
}
void RemovePolypoint(PolylinePoint p) {
PolylinePoint prev = p.Prev;
PolylinePoint next = p.Next;
prev.Next = next;
next.Prev = prev;
}
#endregion
#region Fix collinear neighbors
/// <summary>
/// Fix the situation where a station has two neighbors that are almost in the same directions
/// </summary>
bool GlueCollinearNeighbors(int step) {
HashSet<Point> affectedPoints = new HashSet<Point>();
bool progress = false;
foreach (var node in metroGraphData.Stations)
if (GlueCollinearNeighbors(node, affectedPoints, step)) progress = true;
if (progress) {
//TimeMeasurer.DebugOutput("gluing edges");
metroGraphData.Initialize(false);
SimulatedAnnealing.FixRouting(metroGraphData, bundlingSettings, affectedPoints);
}
return progress;
}
bool GlueCollinearNeighbors(Station node, HashSet<Point> affectedPoints, int step) {
if (node.Neighbors.Length <= 1) return false;
//node,adj => new via point
Dictionary<Tuple<Station, Station>, Point> gluedEdges = new Dictionary<Tuple<Station, Station>, Point>();
var neighbors = node.Neighbors;
for (int i = 0; i < neighbors.Length; i++)
TryToGlueEdges(node, neighbors[i], neighbors[(i + 1) % neighbors.Length], gluedEdges, step);
if (gluedEdges.Count == 0)
return false;
foreach (var keyValuePair in gluedEdges) {
GlueEdge(keyValuePair);
affectedPoints.Add(keyValuePair.Key.Item1.Position);
affectedPoints.Add(keyValuePair.Key.Item2.Position);
affectedPoints.Add(keyValuePair.Value);
}
return true;
}
void TryToGlueEdges(Station node, Station a, Station b, Dictionary<Tuple<Station, Station>, Point> gluedEdges, int step) {
Debug.Assert(a != b);
var angle = Point.Angle(a.Position, node.Position, b.Position);
if (angle < bundlingSettings.AngleThreshold) {
var la = (a.Position - node.Position).Length;
var lb = (b.Position - node.Position).Length;
double ratio = Math.Min(la, lb) / Math.Max(la, lb);
if (ratio < 0.05) return;
if (la < lb) {
if (EdgeGluingIsAllowed(node, a, b)) {
AddEdgeToGlue(node, b, a, a.Position, gluedEdges);
return;
}
}
else {
if (EdgeGluingIsAllowed(node, b, a)) {
AddEdgeToGlue(node, a, b, b.Position, gluedEdges);
return;
}
}
//TODO: need this???
if (step < 5 && ratio > 0.5) {
Point newPosition = ConstructGluingPoint(node, a, b);
if (EdgeGluingIsAllowed(node, a, b, newPosition)) {
AddEdgeToGlue(node, b, a, newPosition, gluedEdges);
}
}
}
}
Point ConstructGluingPoint(Station node, Station a, Station b) {
//temp
double len = Math.Min((a.Position - node.Position).Length, (b.Position - node.Position).Length) / 2;
Point dir = (a.Position - node.Position).Normalize() + (b.Position - node.Position).Normalize();
return node.Position + dir * len / 2;
}
bool EdgeGluingIsAllowed(Station node, Station a, Station b) {
//0. can't pass through real nodes
if (a.IsRealNode || b.IsRealNode) return false;
//1. check intersections) Here we are bending the edge (node->b) to pass through a.Position.
//We need to be sure that segments (node,a) and (a,b) intersect only obstacles enterable for the bundle (node, b)
if (!metroGraphData.cdtIntersections.EdgeIsLegal(a, b, a.Position, b.Position)) return false;
var enterableForEdgeNodeB = metroGraphData.looseIntersections.ObstaclesToIgnoreForBundle(node, b);
var crossingsOfEdgeNodeA = InteractiveEdgeRouter.IntersectionsOfLineAndRectangleNodeOverPolyline(new LineSegment(node.Position, a.Position), metroGraphData.LooseTree);
if (!crossingsOfEdgeNodeA.All(ii => enterableForEdgeNodeB.Contains(ii.Segment1)))
return false;
var crossingsOfEdgeab = InteractiveEdgeRouter.IntersectionsOfLineAndRectangleNodeOverPolyline(new LineSegment(a.Position, b.Position), metroGraphData.LooseTree);
if (!crossingsOfEdgeab.All(ii => enterableForEdgeNodeB.Contains(ii.Segment1)))
return false;
//2. check cost
double delta = ComputeCostDeltaAfterEdgeGluing(node, a, b, a.Position);
if (delta < 0) return false;
return true;
}
bool EdgeGluingIsAllowed(Station node, Station a, Station b, Point gluingPoint) {
//0. can't pass through real nodes
if (!metroGraphData.looseIntersections.HubAvoidsObstacles(gluingPoint, 0, a.EnterableLoosePolylines * b.EnterableLoosePolylines)) return false;
//1. check intersections
if (!metroGraphData.cdtIntersections.EdgeIsLegal(node, null, node.Position, gluingPoint)) return false;
if (!metroGraphData.cdtIntersections.EdgeIsLegal(a, null, a.Position, gluingPoint)) return false;
if (!metroGraphData.cdtIntersections.EdgeIsLegal(b, null, b.Position, gluingPoint)) return false;
//2. check cost
double delta = ComputeCostDeltaAfterEdgeGluing(node, a, b, gluingPoint);
if (delta < 0) return false;
return true;
}
double ComputeCostDeltaAfterEdgeGluing(Station node, Station a, Station b, Point newp) {
double gain = 0;
//ink
double oldInk = metroGraphData.Ink;
double newInk = metroGraphData.Ink - (node.Position - b.Position).Length - (node.Position - a.Position).Length +
(node.Position - newp).Length + (newp - a.Position).Length + (newp - b.Position).Length;
gain += CostCalculator.InkError(oldInk, newInk, bundlingSettings);
//path lengths
foreach (var metroline in metroGraphData.GetIjInfo(node, b).Metrolines) {
double oldLength = metroline.Length;
double newLength = metroline.Length - (node.Position - b.Position).Length +
(node.Position - newp).Length + (newp - b.Position).Length;
gain += CostCalculator.PathLengthsError(oldLength, newLength, metroline.IdealLength, bundlingSettings);
}
foreach (var metroline in metroGraphData.GetIjInfo(node, a).Metrolines) {
double oldLength = metroline.Length;
double newLength = metroline.Length - (node.Position - a.Position).Length +
(node.Position - newp).Length + (newp - a.Position).Length;
gain += CostCalculator.PathLengthsError(oldLength, newLength, metroline.IdealLength, bundlingSettings);
}
//also compute radii gain
//double nowR = Math.Min(GetCurrentHubRadius(node), (node.Position - newp).Length);
//double id2 = HubRadiiCalculator.CalculateIdealHubRadiusWithNeighbors(metroGraphData, bundlingSettings, node);
double id2 = node.cachedIdealRadius;
double nowR = GetCurrentHubRadius(node);
double idealR = HubRadiiCalculator.GetMinRadiusForTwoAdjacentBundles(nowR, node, node.Position, a, b, metroGraphData, bundlingSettings);
if (idealR > nowR) {
gain += CostCalculator.RError(idealR, nowR, bundlingSettings);
}
if (id2 > (node.Position - newp).Length && !node.IsRealNode) {
gain -= CostCalculator.RError(id2, (node.Position - newp).Length, bundlingSettings);
}
return gain;
}
void AddEdgeToGlue(Station node, Station b, Station a, Point newp, Dictionary<Tuple<Station, Station>, Point> gluedEdges) {
//same edge in the reverse direction
if (gluedEdges.ContainsKey(new Tuple<Station, Station>(a, node))) return;
if (gluedEdges.ContainsKey(new Tuple<Station, Station>(b, node))) return;
if (gluedEdges.ContainsKey(new Tuple<Station, Station>(node, a))) return;
if (gluedEdges.ContainsKey(new Tuple<Station, Station>(node, b))) return;
gluedEdges[new Tuple<Station, Station>(node, a)] = newp;
gluedEdges[new Tuple<Station, Station>(node, b)] = newp;
}
void GlueEdge(KeyValuePair<Tuple<Station, Station>, Point> keyValuePair) {
var node = keyValuePair.Key.Item1;
var a = keyValuePair.Key.Item2;
var newp = keyValuePair.Value;
foreach (var polylinePoint in node.MetroNodeInfos.Select(i => i.PolyPoint)) {
if (polylinePoint.Next != null && polylinePoint.Next.Point == a.Position)
SplitPolylinePoint(polylinePoint, newp);
else if (polylinePoint.Prev != null && polylinePoint.Prev.Point == a.Position)
SplitPolylinePoint(polylinePoint.Prev, newp);
}
}
void SplitPolylinePoint(PolylinePoint node, Point pointToInsert) {
if (node.Point == pointToInsert || node.Next.Point == pointToInsert) return;
var p = new PolylinePoint(pointToInsert) { Polyline = node.Polyline, Next = node.Next, Prev = node };
p.Next.Prev = p;
p.Prev.Next = p;
}
#endregion
#region Split edges that are constrained by the obstacles
/// <summary>
/// split each edge that is too much constrained by the obstacles
/// </summary>
bool RelaxConstrainedEdges() {
HashSet<Point> affectedPoints = new HashSet<Point>();
bool progress = false;
foreach (var edge in metroGraphData.VirtualEdges())
if (RelaxConstrainedEdge(edge.Item1, edge.Item2, affectedPoints)) progress = true;
if (progress) {
//TimeMeasurer.DebugOutput("relaxing constrained edges");
metroGraphData.Initialize(false);
SimulatedAnnealing.FixRouting(metroGraphData, bundlingSettings, affectedPoints);
}
return progress;
}
bool RelaxConstrainedEdge(Station a, Station b, HashSet<Point> affectedPoints) {
//find conflicting obstacles
double idealWidth = metroGraphData.GetWidth(a, b, bundlingSettings.EdgeSeparation);
List<Tuple<Point, Point>> closestPoints;
/*bool res =*/ metroGraphData.cdtIntersections.BundleAvoidsObstacles(a, b, a.Position, b.Position, 0.99 * idealWidth / 2.0, out closestPoints);
// Debug.Assert(res); //todo still unsolved
if (closestPoints.Count > 0) {
//find closest obstacle
double bestDist = -1;
Point bestPoint = new Point();
foreach (var d in closestPoints) {
//should not be too close
double distToSegmentEnd = Math.Min((a.Position - d.Item2).Length, (b.Position - d.Item2).Length);
double distAB = (a.Position - b.Position).Length;
double ratio = distToSegmentEnd / distAB;
if (ratio < 0.1) continue;
//choose the closest
double dist = (d.Item1 - d.Item2).Length;
if (bestDist == -1 || dist < bestDist) {
bestDist = dist;
bestPoint = d.Item2;
}
}
if (bestDist == -1) return false;
if (!metroGraphData.looseIntersections.HubAvoidsObstacles(bestPoint, 0, a.EnterableLoosePolylines * b.EnterableLoosePolylines))
return false;
affectedPoints.Add(bestPoint);
affectedPoints.Add(a.Position);
affectedPoints.Add(b.Position);
foreach (var metroline in metroGraphData.GetIjInfo(a, b).Metrolines) {
PolylinePoint pp = null;
//TODO: replace the cycle!
foreach (var ppp in metroline.Polyline.PolylinePoints)
if (ppp.Point == a.Position) {
pp = ppp;
break;
}
Debug.Assert(pp != null);
if (pp.Next != null && pp.Next.Point == b.Position)
SplitPolylinePoint(pp, bestPoint);
else
SplitPolylinePoint(pp.Prev, bestPoint);
}
return true;
}
return false;
}
#endregion
#region Switch flips to reduce path crossings
/// <summary>
/// switch flips
/// </summary>
bool RemoveDoublePathCrossings() {
bool progress = new PathFixer(metroGraphData, metroGraphData.PointIsAcceptableForEdge).Run();
if (progress) {
metroGraphData.Initialize(false);
SimulatedAnnealing.FixRouting(metroGraphData, bundlingSettings);
}
return progress;
}
#endregion
}
}