//
// PortManager.cs
// MSAGL class for Port management for Rectilinear Edge Routing.
//
// Copyright Microsoft Corporation.
using System;
using System.Diagnostics;
using System.Collections.Generic;
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.Routing.Visibility;
namespace Microsoft.Msagl.Routing.Rectilinear {
///
/// This stores information mapping the App-level Ports (e.g. FloatingPort, RelativeFloatingPort,
/// and MultiLocationFloatingPort) to the router's BasicScanPort subclasses (ObstaclePort and FreePoint).
///
internal class PortManager {
// The mapping of Msagl.Port (which may be MultiLocation) to the underlying Obstacle.Shape.
private readonly Dictionary obstaclePortMap = new Dictionary();
// The mapping of Msagl.Port.Location or a Waypoint to a FreePoint with visibility info.
private readonly Dictionary freePointMap = new Dictionary();
// This tracks which locations were used by the last call to RouteEdges, so we can remove unused locations.
private readonly Set freePointLocationsUsedByRouteEdges = new Set();
// Created to wrap the graph for adding transient vertices/edges to the graph.
internal TransientGraphUtility TransUtil { get; set; }
// Owned by RectilinearEdgeRouter.
private readonly VisibilityGraphGenerator graphGenerator;
// Storage and implementation of RectilinearEdgeRouter property of the same name.
internal bool RouteToCenterOfObstacles { get; set; }
// Extension of port visibility splices into the visibility graph.
internal bool LimitPortVisibilitySpliceToEndpointBoundingBox
{
get { return TransUtil.LimitPortVisibilitySpliceToEndpointBoundingBox; }
set { TransUtil.LimitPortVisibilitySpliceToEndpointBoundingBox = value; }
}
// A control point is a source, target, or waypoint (terminology only, there's no ControlPoint
// class). These lists are the control points we've added for the current path.
private readonly List obstaclePortsInGraph = new List();
private readonly Set freePointsInGraph = new Set();
// The limit for edge-chain extension.
private Rectangle portSpliceLimitRectangle;
// The current set of Obstacles that are groups whose boundaries are crossable.
private readonly List activeAncestors = new List();
// Typing shortcuts
private VisibilityGraph VisGraph { get { return graphGenerator.VisibilityGraph; } }
private ScanSegmentTree HScanSegments { get { return graphGenerator.HorizontalScanSegments; } }
private ScanSegmentTree VScanSegments { get { return graphGenerator.VerticalScanSegments; } }
private ObstacleTree ObstacleTree { get { return graphGenerator.ObstacleTree; } }
private Dictionary> AncestorSets { get { return ObstacleTree.AncestorSets; } }
internal PortManager(VisibilityGraphGenerator graphGenerator) {
this.TransUtil = new TransientGraphUtility(graphGenerator);
this.graphGenerator = graphGenerator;
}
internal void Clear() {
TransUtil.RemoveFromGraph(); // Probably nothing in here when this is called
obstaclePortMap.Clear();
}
internal void CreateObstaclePorts(Obstacle obstacle) {
// Create ObstaclePorts for all Ports of this obstacle. This just creates the
// ObstaclePort object; we don't add its edges/vertices to the graph until we
// do the actual routing.
foreach (var port in obstacle.Ports) {
CreateObstaclePort(obstacle, port);
}
}
private ObstaclePort CreateObstaclePort(Obstacle obstacle, Port port) {
// This will replace any previous specification for the port (last one wins).
Debug.Assert(!obstaclePortMap.ContainsKey(port), "Port is used by more than one obstacle");
if (null == port.Curve) {
return null;
}
var roundedLocation = ApproximateComparer.Round(port.Location);
if (PointLocation.Outside == Curve.PointRelativeToCurveLocation(roundedLocation, obstacle.InputShape.BoundaryCurve)) {
// Obstacle.Port is outside Obstacle.Shape; handle it as a FreePoint.
return null;
}
if ((obstacle.InputShape.BoundaryCurve != port.Curve)
&& (PointLocation.Outside == Curve.PointRelativeToCurveLocation(roundedLocation, port.Curve))) {
// Obstacle.Port is outside port.Curve; handle it as a FreePoint.
return null;
}
var oport = new ObstaclePort(port, obstacle);
obstaclePortMap[port] = oport;
return oport;
}
internal List FindVertices(Port port) {
var vertices = new List();
ObstaclePort oport;
if (obstaclePortMap.TryGetValue(port, out oport)) {
if (RouteToCenterOfObstacles) {
vertices.Add(oport.CenterVertex);
return vertices;
}
// Add all vertices on the obstacle borders. Avoid LINQ for performance.
foreach (var entrance in oport.PortEntrances) {
VisibilityVertex vertex = this.VisGraph.FindVertex(entrance.UnpaddedBorderIntersect);
if (null != vertex) {
vertices.Add(vertex);
}
}
return vertices;
}
vertices.Add(VisGraph.FindVertex(ApproximateComparer.Round(port.Location)));
return vertices;
}
internal void RemoveObstaclePorts(Obstacle obstacle) {
foreach (var port in obstacle.Ports) {
// Since we remove the port from the visibility graph after each routing, all we
// have to do here is remove it from the dictionary.
RemoveObstaclePort(port);
}
}
void RemoveObstaclePort(Port port) {
obstaclePortMap.Remove(port);
}
// Add path control points - source, target, and any waypoints.
internal void AddControlPointsToGraph(EdgeGeometry edgeGeom, Dictionary shapeToObstacleMap) {
this.GetPortSpliceLimitRectangle(edgeGeom);
activeAncestors.Clear();
ObstaclePort sourceOport, targetOport;
var ssAncs = FindAncestorsAndObstaclePort(edgeGeom.SourcePort, out sourceOport);
var ttAncs = FindAncestorsAndObstaclePort(edgeGeom.TargetPort, out targetOport);
if ((AncestorSets.Count > 0) && (null != sourceOport) && (null != targetOport)) {
// Make non-common ancestors' boundaries transparent (we don't want to route outside common ancestors).
var ttAncsOnly = ttAncs - ssAncs;
var ssAncsOnly = ssAncs - ttAncs;
ActivateAncestors(ssAncsOnly, ttAncsOnly, shapeToObstacleMap);
}
// Now that we've set any active ancestors, splice in the port visibility.
AddPortToGraph(edgeGeom.SourcePort, sourceOport);
AddPortToGraph(edgeGeom.TargetPort, targetOport);
AddWaypointsToGraph(edgeGeom);
}
private void AddWaypointsToGraph(EdgeGeometry edgeGeom) {
if (null == edgeGeom.Waypoints) {
return;
}
// Waypoints form part of a composite path and therefore we want more than just a single entry to out-of-bounds
// waypoints, so we can go out from graph, through waypoint, and back to the graph without backtracking along the
// same edge. If two waypoints are out of bounds to the same side, make sure there is a single-bend (or 0-bend
// if collinear) path between them.
FreePoint lastOobWaypoint = null;
FreePoint firstWaypoint = null;
foreach (var point in edgeGeom.Waypoints) {
var freePoint = this.AddFreePointToGraph(point);
if (firstWaypoint == null) {
firstWaypoint = freePoint;
}
if (!freePoint.IsOutOfBounds) {
lastOobWaypoint = null;
continue;
}
if (null != lastOobWaypoint) {
if (!this.ObstacleTree.GraphBox.Intersects(new Rectangle(lastOobWaypoint.Point, freePoint.Point))) {
// They are oob to the same side.
var dirs = PointComparer.GetDirections(lastOobWaypoint.Point, freePoint.Point);
if (CompassVector.IsPureDirection(dirs)) {
this.TransUtil.ConnectVertexToTargetVertex(lastOobWaypoint.Vertex, freePoint.Vertex, dirs, ScanSegment.NormalWeight);
} else {
this.TransUtil.ConnectVertexToTargetVertex(lastOobWaypoint.Vertex, freePoint.Vertex,
dirs & (Directions.North | Directions.South), ScanSegment.NormalWeight);
this.TransUtil.ConnectVertexToTargetVertex(lastOobWaypoint.Vertex, freePoint.Vertex,
dirs & (Directions.East | Directions.West), ScanSegment.NormalWeight);
}
} else {
// They are both out of bounds but not to the same side, so create a single turn around the corner.
// Nudger doesn't seem to always remove the staircase if we don't. Don't bother if they are at
// opposite sides of the graph, including diagonal corners.
if (PointComparer.IsPureDirection(freePoint.OutOfBoundsDirectionFromGraph)
&& PointComparer.IsPureDirection(lastOobWaypoint.OutOfBoundsDirectionFromGraph)
&& (lastOobWaypoint.OutOfBoundsDirectionFromGraph != CompassVector.OppositeDir(freePoint.OutOfBoundsDirectionFromGraph))) {
this.TransUtil.ConnectVertexToTargetVertex(freePoint.Vertex, lastOobWaypoint.Vertex,
CompassVector.OppositeDir(freePoint.OutOfBoundsDirectionFromGraph), ScanSegment.NormalWeight);
}
}
}
lastOobWaypoint = freePoint;
}
ConnectOobWaypointToEndpointVisibilityAtGraphBoundary(firstWaypoint, edgeGeom.SourcePort);
ConnectOobWaypointToEndpointVisibilityAtGraphBoundary(lastOobWaypoint, edgeGeom.TargetPort);
}
private void ConnectOobWaypointToEndpointVisibilityAtGraphBoundary(FreePoint oobWaypoint, Port port) {
if ((oobWaypoint == null) || !oobWaypoint.IsOutOfBounds) {
return;
}
// Connect to the graphbox side at points collinear with the vertices. The waypoint may be
// OOB in two directions so call once for each axis.
var endpointVertices = this.FindVertices(port);
var dirFromGraph = oobWaypoint.OutOfBoundsDirectionFromGraph & (Directions.North | Directions.South);
ConnectToGraphAtPointsCollinearWithVertices(oobWaypoint, dirFromGraph, endpointVertices);
dirFromGraph = oobWaypoint.OutOfBoundsDirectionFromGraph & (Directions.East | Directions.West);
ConnectToGraphAtPointsCollinearWithVertices(oobWaypoint, dirFromGraph, endpointVertices);
}
private void ConnectToGraphAtPointsCollinearWithVertices(FreePoint oobWaypoint, Directions dirFromGraph,
List endpointVertices) {
if (Directions. None == dirFromGraph) {
// Not out of bounds on this axis.
return;
}
var dirToGraph = CompassVector.OppositeDir(dirFromGraph);
foreach (var vertex in endpointVertices) {
var graphBorderLocation = this.InBoundsGraphBoxIntersect(vertex.Point, dirFromGraph);
var graphBorderVertex = this.VisGraph.FindVertex(graphBorderLocation);
if (graphBorderVertex != null) {
this.TransUtil.ConnectVertexToTargetVertex(oobWaypoint.Vertex, graphBorderVertex, dirToGraph, ScanSegment.NormalWeight);
}
}
}
internal bool SetAllAncestorsActive(EdgeGeometry edgeGeom, Dictionary shapeToObstacleMap) {
if (0 == AncestorSets.Count) {
return false;
}
ObstacleTree.AdjustSpatialAncestors();
ClearActiveAncestors();
ObstaclePort sourceOport, targetOport;
var ssAncs = FindAncestorsAndObstaclePort(edgeGeom.SourcePort, out sourceOport);
var ttAncs = FindAncestorsAndObstaclePort(edgeGeom.TargetPort, out targetOport);
if ((AncestorSets.Count > 0) && (null != ssAncs) && (null != ttAncs)) {
// Make all ancestors boundaries transparent; in this case we've already tried with only
// non-common and found no path, so perhaps an obstacle is outside its parent group's bounds.
ActivateAncestors(ssAncs, ttAncs, shapeToObstacleMap);
return true;
}
return false;
}
internal void SetAllGroupsActive() {
// We couldn't get a path when we activated all hierarchical and spatial group ancestors of the shapes,
// so assume we may be landlocked and activate all groups, period.
ClearActiveAncestors();
foreach (var group in ObstacleTree.GetAllGroups()) {
group.IsTransparentAncestor = true;
activeAncestors.Add(group);
}
}
internal Set FindAncestorsAndObstaclePort(Port port, out ObstaclePort oport) {
oport = FindObstaclePort(port);
if (0 == AncestorSets.Count) {
return null;
}
if (null != oport) {
return AncestorSets[oport.Obstacle.InputShape];
}
// This is a free Port (not associated with an obstacle) or a Waypoint; return all spatial parents.
return new Set(ObstacleTree.Root.AllHitItems(new Rectangle(port.Location, port.Location), shape => shape.IsGroup)
.Select(obs => obs.InputShape));
}
private void ActivateAncestors(Set ssAncsToUse, Set ttAncsToUse, Dictionary shapeToObstacleMap) {
foreach (var shape in ssAncsToUse + ttAncsToUse) {
var group = shapeToObstacleMap[shape];
Debug.Assert(group.IsGroup, "Ancestor shape is not a group");
group.IsTransparentAncestor = true;
activeAncestors.Add(group);
}
}
void ClearActiveAncestors() {
foreach (var group in activeAncestors) {
group.IsTransparentAncestor = false;
}
activeAncestors.Clear();
}
internal void RemoveControlPointsFromGraph() {
ClearActiveAncestors();
RemoveObstaclePortsFromGraph();
RemoveFreePointsFromGraph();
TransUtil.RemoveFromGraph();
this.portSpliceLimitRectangle = new Rectangle();
}
private void RemoveObstaclePortsFromGraph() {
foreach (var oport in this.obstaclePortsInGraph) {
oport.RemoveFromGraph();
}
this.obstaclePortsInGraph.Clear();
}
private void RemoveFreePointsFromGraph() {
foreach (var freePoint in this.freePointsInGraph) {
freePoint.RemoveFromGraph();
}
this.freePointsInGraph.Clear();
}
private void RemoveStaleFreePoints() {
// FreePoints are not necessarily persistent - they may for example be waypoints which are removed.
// So after every routing pass, remove any that were not added to the graph. Because the FreePoint has
// be removed from the graph, its Vertex (and thus Point) are no longer set in the FreePoint, so we
// must use the key from the dictionary.
if (this.freePointMap.Count > this.freePointLocationsUsedByRouteEdges.Count) {
var staleFreePairs = this.freePointMap.Where(kvp => !this.freePointLocationsUsedByRouteEdges.Contains(kvp.Key)).ToArray();
foreach (var staleFreePair in staleFreePairs) {
this.freePointMap.Remove(staleFreePair.Key);
}
}
}
internal void ClearVisibility() {
// Most of the retained freepoint stuff is about precalculated visibility.
this.freePointMap.Clear();
foreach (var oport in obstaclePortMap.Values) {
oport.ClearVisibility();
}
}
internal void BeginRouteEdges() {
this.RemoveControlPointsFromGraph(); // ensure there are no leftovers
this.freePointLocationsUsedByRouteEdges.Clear();
}
internal void EndRouteEdges() {
this.RemoveStaleFreePoints();
}
internal ObstaclePort FindObstaclePort(Port port) {
ObstaclePort oport;
if (obstaclePortMap.TryGetValue(port, out oport)) {
// First see if the obstacle's port list has changed without UpdateObstacles() being called.
// Unfortunately we don't have a way to update the obstacle's ports until we enter
// this block; there is no direct Port->Shape/Obstacle mapping. So UpdateObstacle must still
// be called, but at least this check here will remove obsolete ObstaclePorts.
Set addedPorts, removedPorts;
if (oport.Obstacle.GetPortChanges(out addedPorts, out removedPorts)) {
foreach (var newPort in addedPorts) {
CreateObstaclePort(oport.Obstacle, newPort);
}
foreach (var oldPort in removedPorts) {
RemoveObstaclePort(oldPort);
}
// If it's not still there, it was moved outside the obstacle so we'll just add it as a FreePoint.
if (!obstaclePortMap.TryGetValue(port, out oport)) {
oport = null;
}
}
}
return oport;
}
private void AddPortToGraph(Port port, ObstaclePort oport) {
if (null != oport) {
AddObstaclePortToGraph(oport);
return;
}
// This is a FreePoint, either a Waypoint or a Port not in an Obstacle.Ports list.
AddFreePointToGraph(port.Location);
}
private void AddObstaclePortToGraph(ObstaclePort oport) {
// If the port's position has changed without UpdateObstacles() being called, recreate it.
if (oport.LocationHasChanged) {
RemoveObstaclePort(oport.Port);
oport = CreateObstaclePort(oport.Obstacle, oport.Port);
if (null == oport)
{
// Port has been moved outside obstacle; return and let caller add it as a FreePoint.
return;
}
}
oport.AddToGraph(TransUtil, RouteToCenterOfObstacles);
obstaclePortsInGraph.Add(oport);
this.CreateObstaclePortEntrancesIfNeeded(oport);
// We've determined the entrypoints on the obstacle boundary for each PortEntry,
// so now add them to the VisGraph.
foreach (var entrance in oport.PortEntrances) {
AddObstaclePortEntranceToGraph(entrance);
}
return;
}
private void CreateObstaclePortEntrancesIfNeeded(ObstaclePort oport) {
if (0 != oport.PortEntrances.Count) {
return;
}
// Create the PortEntrances with initial information: border intersect and outer edge direction.
if (null == oport.Port.PortEntry) {
this.CreateObstaclePortEntrancesFromPoints(oport);
} else {
this.CreateObstaclePortEntrancesFromPortEntry(oport);
}
}
public Point[] GetPortVisibilityIntersection(EdgeGeometry edgeGeometry) {
var sourceOport = this.FindObstaclePort(edgeGeometry.SourcePort);
var targetOport = this.FindObstaclePort(edgeGeometry.TargetPort);
if ((sourceOport == null) || (targetOport == null)) {
return null;
}
if (sourceOport.Obstacle.IsInConvexHull || targetOport.Obstacle.IsInConvexHull) {
return null;
}
this.CreateObstaclePortEntrancesIfNeeded(sourceOport);
this.CreateObstaclePortEntrancesIfNeeded(targetOport);
if (!sourceOport.VisibilityRectangle.Intersects(targetOport.VisibilityRectangle)) {
return null;
}
foreach (var sourceEntrance in sourceOport.PortEntrances) {
if (!sourceEntrance.WantVisibilityIntersection) {
continue;
}
foreach (var targetEntrance in targetOport.PortEntrances) {
if (!targetEntrance.WantVisibilityIntersection) {
continue;
}
var points = (sourceEntrance.IsVertical == targetEntrance.IsVertical)
? GetPathPointsFromOverlappingCollinearVisibility(sourceEntrance, targetEntrance)
: GetPathPointsFromIntersectingVisibility(sourceEntrance, targetEntrance);
if (points != null) {
return points;
}
}
}
return null;
}
private static Point[] GetPathPointsFromOverlappingCollinearVisibility(ObstaclePortEntrance sourceEntrance, ObstaclePortEntrance targetEntrance) {
// If the segments are the same they'll be in reverse. Note: check for IntervalsOverlap also, if we support FreePoints here.
if (!StaticGraphUtility.IntervalsAreSame(sourceEntrance.MaxVisibilitySegment.Start, sourceEntrance.MaxVisibilitySegment.End,
targetEntrance.MaxVisibilitySegment.End, targetEntrance.MaxVisibilitySegment.Start)) {
return null;
}
if (sourceEntrance.HasGroupCrossings || targetEntrance.HasGroupCrossings) {
return null;
}
if (PointComparer.Equal(sourceEntrance.UnpaddedBorderIntersect, targetEntrance.UnpaddedBorderIntersect)) {
// Probably one obstacle contained within another; we handle that elsewhere.
return null;
}
return new[] {
sourceEntrance.UnpaddedBorderIntersect,
targetEntrance.UnpaddedBorderIntersect
};
}
private static Point[] GetPathPointsFromIntersectingVisibility(ObstaclePortEntrance sourceEntrance, ObstaclePortEntrance targetEntrance) {
Point intersect;
if (!StaticGraphUtility.SegmentsIntersect(sourceEntrance.MaxVisibilitySegment, targetEntrance.MaxVisibilitySegment, out intersect)) {
return null;
}
if (sourceEntrance.HasGroupCrossingBeforePoint(intersect) || targetEntrance.HasGroupCrossingBeforePoint(intersect)) {
return null;
}
return new[] {
sourceEntrance.UnpaddedBorderIntersect,
intersect,
targetEntrance.UnpaddedBorderIntersect
};
}
private void CreateObstaclePortEntrancesFromPoints(ObstaclePort oport) {
var graphBox = graphGenerator.ObstacleTree.GraphBox;
var curveBox = new Rectangle(ApproximateComparer.Round(oport.PortCurve.BoundingBox.LeftBottom)
, ApproximateComparer.Round(oport.PortCurve.BoundingBox.RightTop));
// This Port does not have a PortEntry, so we'll have visibility edges to its location
// in the Horizontal and Vertical directions (possibly all 4 directions, if not on boundary).
//
// First calculate the intersection with the obstacle in all directions. Do nothing in the
// horizontal direction for port locations that are on the unpadded vertical extremes, because
// this will have a path that moves alongside a rectilinear obstacle side in less than the
// padding radius and will thus create the PaddedBorderIntersection on the side rather than top
// (and vice-versa for the vertical direction). We'll have an edge in the vertical direction
// to the padded extreme boundary ScanSegment, and the Nudger will modify paths as appropriate
// to remove unnecessary bends.
// Use the unrounded port location to intersect with its curve.
Point location = ApproximateComparer.Round(oport.PortLocation);
Point xx0, xx1;
bool found = false;
if (!PointComparer.Equal(location.Y, curveBox.Top)
&& !PointComparer.Equal(location.Y, curveBox.Bottom)) {
found = true;
var hSeg = new LineSegment(graphBox.Left, location.Y, graphBox.Right, location.Y);
GetBorderIntersections(location, hSeg, oport.PortCurve, out xx0, out xx1);
var wBorderIntersect = new Point(Math.Min(xx0.X, xx1.X), location.Y);
if (wBorderIntersect.X < curveBox.Left) { // Handle rounding error
wBorderIntersect.X = curveBox.Left;
}
var eBorderIntersect = new Point(Math.Max(xx0.X, xx1.X), location.Y);
if (eBorderIntersect.X > curveBox.Right) {
eBorderIntersect.X = curveBox.Right;
}
CreatePortEntrancesAtBorderIntersections(curveBox, oport, location, wBorderIntersect, eBorderIntersect);
} // endif horizontal pass is not at vertical extreme
if (!PointComparer.Equal(location.X, curveBox.Left)
&& !PointComparer.Equal(location.X, curveBox.Right)) {
found = true;
var vSeg = new LineSegment(location.X, graphBox.Bottom, location.X, graphBox.Top);
GetBorderIntersections(location, vSeg, oport.PortCurve, out xx0, out xx1);
var sBorderIntersect = new Point(location.X, Math.Min(xx0.Y, xx1.Y));
if (sBorderIntersect.Y < graphBox.Bottom) { // Handle rounding error
sBorderIntersect.Y = graphBox.Bottom;
}
var nBorderIntersect = new Point(location.X, Math.Max(xx0.Y, xx1.Y));
if (nBorderIntersect.Y > graphBox.Top) {
nBorderIntersect.Y = graphBox.Top;
}
CreatePortEntrancesAtBorderIntersections(curveBox, oport, location, sBorderIntersect, nBorderIntersect);
} // endif vertical pass is not at horizontal extreme
if (!found) {
// This must be on a corner, else one of the above would have matched.
this.CreateEntrancesForCornerPort(curveBox, oport, location);
}
}
private void CreateObstaclePortEntrancesFromPortEntry(ObstaclePort oport) {
// Create a PortEntrance for each of one or more midpoints of each of the spans of the PortEntry.
foreach (var midPoint in oport.Port.PortEntry.GetEntryPoints()) {
var borderPoint = ApproximateComparer.Round(midPoint);
Directions derivDirs = CompassVector.VectorDirection(GetDerivative(oport, borderPoint));
// The deriv is clockwise so if it moves East then it is on top and Port visibility extends to
// the North; otherwise it extends to the South. Similarly, if the deriv moves South then visibility
// must extend to the East, else West. If it is flat or perpendicular then visibility extends in
// only one direction. Due to clockwiseness, we can just RotateLeft to get to the desired extension.
Directions dir = derivDirs & (Directions.North | Directions.South);
if (Directions. None != dir) {
oport.CreatePortEntrance(borderPoint, CompassVector.RotateLeft(dir), this.ObstacleTree);
}
dir = derivDirs & (Directions.East | Directions.West);
if (Directions. None != dir) {
oport.CreatePortEntrance(borderPoint, CompassVector.RotateLeft(dir), this.ObstacleTree);
}
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
private void GetBorderIntersections(Point location, LineSegment lineSeg, ICurve curve
, out Point xx0, out Point xx1) {
// Important: the LineSegment must be the first arg to GetAllIntersections so RawIntersection works.
IList xxs = Curve.GetAllIntersections(lineSeg, curve, true /*liftIntersections*/);
StaticGraphUtility.Assert(2 == xxs.Count, "Expected two intersections", this.ObstacleTree, this.VisGraph);
xx0 = SpliceUtility.RawIntersection(xxs[0], location);
xx1 = SpliceUtility.RawIntersection(xxs[1], location);
}
private void CreatePortEntrancesAtBorderIntersections(Rectangle curveBox, ObstaclePort oport, Point location
, Point unpaddedBorderIntersect0, Point unpaddedBorderIntersect1) {
// Allow entry from both sides, except from the opposite side of a point on the border.
Directions dir = PointComparer.GetPureDirection(unpaddedBorderIntersect0, unpaddedBorderIntersect1);
if (!PointComparer.Equal(unpaddedBorderIntersect0, location)) {
CreatePortEntrance(curveBox, oport, unpaddedBorderIntersect1, dir);
}
if (!PointComparer.Equal(unpaddedBorderIntersect1, location)) {
CreatePortEntrance(curveBox, oport, unpaddedBorderIntersect0, CompassVector.OppositeDir(dir));
}
}
private static Point GetDerivative(ObstaclePort oport, Point borderPoint) {
// This is only used for ObstaclePorts, which have ensured Port.Curve is not null.
double param = oport.PortCurve.ClosestParameter(borderPoint);
var deriv = oport.PortCurve.Derivative(param);
var parMid = (oport.PortCurve.ParStart + oport.PortCurve.ParEnd) / 2;
if (!InteractiveObstacleCalculator.CurveIsClockwise(oport.PortCurve, oport.PortCurve[parMid])) {
deriv = -deriv;
}
return deriv;
}
private void CreatePortEntrance(Rectangle curveBox, ObstaclePort oport, Point unpaddedBorderIntersect, Directions outDir) {
oport.CreatePortEntrance(unpaddedBorderIntersect, outDir, this.ObstacleTree);
ScanDirection scanDir = ScanDirection.GetInstance(outDir);
double axisDistanceBetweenIntersections = StaticGraphUtility.GetRectangleBound(curveBox, outDir) - scanDir.Coord(unpaddedBorderIntersect);
if (axisDistanceBetweenIntersections < 0.0) {
axisDistanceBetweenIntersections = -axisDistanceBetweenIntersections;
}
if (axisDistanceBetweenIntersections > ApproximateComparer.IntersectionEpsilon) {
// This is not on an extreme boundary of the unpadded curve (it's on a sloping (nonrectangular) boundary),
// so we need to generate another entrance in one of the perpendicular directions (depending on which
// way the side slopes). Derivative is always clockwise.
Directions perpDirs = CompassVector.VectorDirection(GetDerivative(oport, unpaddedBorderIntersect));
Directions perpDir = perpDirs & ~(outDir | CompassVector.OppositeDir(outDir));
if (Directions. None != (outDir & perpDirs)) {
// If the derivative is in the same direction as outDir then perpDir is toward the obstacle
// interior and must be reversed.
perpDir = CompassVector.OppositeDir(perpDir);
}
oport.CreatePortEntrance(unpaddedBorderIntersect, perpDir, this.ObstacleTree);
}
}
private void CreateEntrancesForCornerPort(Rectangle curveBox, ObstaclePort oport, Point location) {
// This must be a corner or it would have been within one of the bounds and handled elsewhere.
// Therefore create an entrance in both directions, with the first direction selected so that
// the second can be obtained via RotateRight.
Directions outDir = Directions.North;
if (PointComparer.Equal(location, curveBox.LeftBottom)) {
outDir = Directions.South;
}
else if (PointComparer.Equal(location, curveBox.LeftTop)) {
outDir = Directions.West;
}
else if (PointComparer.Equal(location, curveBox.RightTop)) {
outDir = Directions.North;
}
else if (PointComparer.Equal(location, curveBox.RightBottom)) {
outDir = Directions.East;
}
else {
Debug.Assert(false, "Expected Port to be on corner of curveBox");
}
oport.CreatePortEntrance(location, outDir, this.ObstacleTree);
oport.CreatePortEntrance(location, CompassVector.RotateRight(outDir), this.ObstacleTree);
}
private void AddObstaclePortEntranceToGraph(ObstaclePortEntrance entrance) {
// Note: As discussed in ObstaclePortEntrance.AddToGraph, oport.VisibilityBorderIntersect may be
// on a border shared with another obstacle, in which case we'll extend into that obstacle. This
// should be fine if we're consistent about "touching means overlapped", so that a path that comes
// through the other obstacle on the shared border is OK.
VisibilityVertex borderVertex = VisGraph.FindVertex(entrance.VisibilityBorderIntersect);
if (null != borderVertex) {
entrance.ExtendFromBorderVertex(TransUtil, borderVertex, this.portSpliceLimitRectangle, RouteToCenterOfObstacles);
return;
}
// There may be no scansegment to splice to before we hit an adjacent obstacle, so if the edge
// is null there is nothing to do.
VisibilityVertex targetVertex;
double weight = entrance.IsOverlapped ? ScanSegment.OverlappedWeight : ScanSegment.NormalWeight;
VisibilityEdge edge = this.FindorCreateNearestPerpEdge(entrance.MaxVisibilitySegment.End, entrance.VisibilityBorderIntersect,
entrance.OutwardDirection, weight /*checkForObstacle*/, out targetVertex);
if (null != edge) {
entrance.AddToAdjacentVertex(TransUtil, targetVertex, this.portSpliceLimitRectangle, RouteToCenterOfObstacles);
}
}
private Point InBoundsGraphBoxIntersect(Point point, Directions dir) {
return StaticGraphUtility.RectangleBorderIntersect(graphGenerator.ObstacleTree.GraphBox, point, dir);
}
private VisibilityEdge FindorCreateNearestPerpEdge(Point first, Point second, Directions dir, double weight) {
VisibilityVertex targetVertex;
return this.FindorCreateNearestPerpEdge(first, second, dir, weight, out targetVertex);
}
private VisibilityEdge FindorCreateNearestPerpEdge(Point first, Point second, Directions dir, double weight, out VisibilityVertex targetVertex) {
// Find the closest perpendicular ScanSegment that intersects a segment with endpoints
// first and second, then find the closest parallel ScanSegment that intersects that
// perpendicular ScanSegment. This gives us a VisibilityVertex location from which we
// can walk to the closest perpendicular VisibilityEdge that intersects first->second.
Tuple couple = StaticGraphUtility.SortAscending(first, second);
Point low = couple.Item1;
Point high = couple.Item2;
ScanSegmentTree perpendicularScanSegments = StaticGraphUtility.IsVertical(dir) ? HScanSegments : VScanSegments;
// Look up the nearest intersection. For obstacles, we cannot just look for the bounding box
// corners because nonrectilinear obstacles may have other obstacles overlapping the bounding
// box (at either the corners or between the port border intersection and the bounding box
// side), and of course obstacles may overlap too.
ScanSegment nearestPerpSeg = StaticGraphUtility.IsAscending(dir)
? perpendicularScanSegments.FindLowestIntersector(low, high)
: perpendicularScanSegments.FindHighestIntersector(low, high);
if (nearestPerpSeg == null) {
// No ScanSegment between this and visibility limits.
targetVertex = null;
return null;
}
Point edgeIntersect = StaticGraphUtility.SegmentIntersection(nearestPerpSeg, low);
// We now know the nearest perpendicular segment that intersects start->end. Next we'll find a close
// parallel scansegment that intersects the perp segment, then walk to find the nearest perp edge.
return FindOrCreateNearestPerpEdgeFromNearestPerpSegment(StaticGraphUtility.IsAscending(dir) ? low : high,
nearestPerpSeg, edgeIntersect, weight, out targetVertex);
}
private VisibilityEdge FindOrCreateNearestPerpEdgeFromNearestPerpSegment(Point pointLocation, ScanSegment scanSeg,
Point edgeIntersect, double weight, out VisibilityVertex targetVertex) {
// Given: a ScanSegment scanSeg perpendicular to pointLocation->edgeIntersect and containing edgeIntersect.
// To find: a VisibilityEdge perpendicular to pointLocation->edgeIntersect which may be on scanSeg, or may
// be closer to pointLocation than the passed edgeIntersect is.
// Since there may be TransientEdges between pointLocation and edgeIntersect, we start by finding
// a scanSeg-intersecting (i.e. parallel to pointLocation->edgeIntersect) ScanSegment, then starting from
// the intersection of those segments, walk the VisibilityGraph until we find the closest VisibilityEdge
// perpendicular to pointLocation->edgeIntersect. If there is a vertex on that edge collinear to
// pointLocation->edgeIntersect, return the edge for which it is Source, else split the edge.
// If there is already a vertex at edgeIntersect, we do not need to look for the intersecting ScanSegment.
VisibilityVertex segsegVertex = VisGraph.FindVertex(edgeIntersect);
if (null == segsegVertex) {
var edge = this.FindOrCreateSegmentIntersectionVertexAndAssociatedEdge(pointLocation, edgeIntersect, scanSeg, weight,
out segsegVertex, out targetVertex);
if (edge != null) {
return edge;
}
}
else if (PointComparer.Equal(pointLocation, edgeIntersect)) {
// The initial pointLocation was on scanSeg at an existing vertex so return an edge
// from that vertex along scanSeg. Look in both directions in case of dead ends.
targetVertex = segsegVertex;
return TransUtil.FindNextEdge(targetVertex, scanSeg.ScanDirection.Direction)
?? TransUtil.FindNextEdge(targetVertex, CompassVector.OppositeDir(scanSeg.ScanDirection.Direction));
}
// pointLocation is not on the initial scanSeg, so see if there is a transient edge between
// pointLocation and edgeIntersect. edgeIntersect == segsegVertex.Point if pointLocation is
// collinear with intSegBefore (pointLocation is before or after intSegBefore's VisibilityVertices).
Directions dirTowardLocation = PointComparer.GetPureDirection(edgeIntersect, pointLocation);
Directions perpDir = PointComparer.GetDirections(segsegVertex.Point, pointLocation);
if (dirTowardLocation == perpDir) {
// intSegBefore is collinear with pointLocation so walk to the vertex closest to pointLocation.
VisibilityVertex bracketTarget;
TransientGraphUtility.FindBracketingVertices(segsegVertex, pointLocation, dirTowardLocation
, out targetVertex, out bracketTarget);
// Return an edge. Look in both directions in case of dead ends.
return TransUtil.FindNextEdge(targetVertex, CompassVector.RotateLeft(dirTowardLocation))
?? TransUtil.FindNextEdge(targetVertex, CompassVector.RotateRight(dirTowardLocation));
}
// Now make perpDir have only the perpendicular component.
perpDir &= ~dirTowardLocation; // if this is Directions. None, pointLocation == edgeIntersect
StaticGraphUtility.Assert(Directions. None != perpDir
, "pointLocation == initial segsegVertex.Point should already have exited", ObstacleTree, VisGraph);
// Other TransientVE edge chains may have been added between the control point and the
// ScanSegment (which is always non-transient), and they may have split ScanSegment VEs.
// Fortunately we know we'll always have all transient edge chains extended to or past any
// control point (due to LimitRectangle), so we can just move up lowestIntSeg toward
// pointLocation, updating segsegVertex and edgeIntersect. There are 3 possibilities:
// - location is not on an edge - the usual case, we just create an edge perpendicular
// to an edge on scanSeg, splitting that scanSeg edge in the process.
// - location is on a VE that is parallel to scanSeg. This is essentially the same thing
// but we don't need the first perpendicular edge to scanSeg.
// - location is on a VE that is perpendicular to scanSeg. In that case the vertex on ScanSeg
// already exists; TransUtil.FindOrAddEdge just returns the edge starting at that intersection.
// FreePoint tests of this are in RectilinearTests.FreePortLocationRelativeToTransientVisibilityEdges*.
VisibilityEdge perpendicularEdge = TransUtil.FindNearestPerpendicularOrContainingEdge(segsegVertex, perpDir, pointLocation);
if (null == perpendicularEdge) {
// Dead end; we're above the highest point at which there is an intersection of scanSeg.
// Create a new vertex and edge higher than the ScanSegment's HighestVisibilityVertex
// if that doesn't cross an obstacle (if we are between two ScanSegment dead-ends, we may).
// We hit this in RectilinearFileTests.Nudger_Many_Paths_In_Channel and .Nudger_Overlap*.
StaticGraphUtility.Assert(edgeIntersect > scanSeg.HighestVisibilityVertex.Point
, "edgeIntersect is not > scanSeg.HighestVisibilityVertex", ObstacleTree, VisGraph);
targetVertex = TransUtil.AddVertex(edgeIntersect);
return TransUtil.FindOrAddEdge(targetVertex, scanSeg.HighestVisibilityVertex, scanSeg.Weight);
}
// We have an intersecting perp edge, which may be on the original scanSeg or closer to pointLocation.
// Get one of its vertices and re-find the intersection on it (it doesn't matter which vertex of the
// edge we use, but for consistency use the "lower in perpDir" one).
segsegVertex = StaticGraphUtility.GetVertex(perpendicularEdge, CompassVector.OppositeDir(perpDir));
edgeIntersect = StaticGraphUtility.SegmentIntersection(pointLocation, edgeIntersect, segsegVertex.Point);
// By this point we've verified there's no intervening Transient edge, so if we have an identical
// point, we're done.
if (PointComparer.Equal(segsegVertex.Point, edgeIntersect)) {
targetVertex = segsegVertex;
return TransUtil.FindNextEdge(segsegVertex, perpDir);
}
// The targetVertex doesn't exist; this will split the edge and add it.
targetVertex = TransUtil.FindOrAddVertex(edgeIntersect);
return TransUtil.FindOrAddEdge(segsegVertex, targetVertex, weight);
}
private VisibilityEdge FindOrCreateSegmentIntersectionVertexAndAssociatedEdge(Point pointLocation, Point edgeIntersect, ScanSegment scanSeg,
double weight, out VisibilityVertex segsegVertex, out VisibilityVertex targetVertex) {
ScanSegmentTree intersectingSegments = scanSeg.IsVertical ? this.HScanSegments : this.VScanSegments;
ScanSegment intSegBefore = intersectingSegments.FindHighestIntersector(scanSeg.Start, edgeIntersect);
if (null == intSegBefore) {
// Dead end; we're below the lowest point at which there is an intersection of scanSeg.
// Create a new vertex and edge lower than the ScanSegment's LowestVisibilityVertex.
// Test: RectilinearFileTests.Overlap_Rotate_SplicePort_FreeObstaclePorts.
segsegVertex = null;
targetVertex = this.TransUtil.AddVertex(edgeIntersect);
return this.TransUtil.FindOrAddEdge(targetVertex, scanSeg.LowestVisibilityVertex, scanSeg.Weight);
}
// Get the VisibilityVertex at the intersection of the two segments we just found;
// edgeIntersect is between that vertex and another on the segment, and we'll split
// the edge between those two vertices (or find one nearer to walk to).
Point segsegIntersect = StaticGraphUtility.SegmentIntersection(scanSeg, intSegBefore);
segsegVertex = this.VisGraph.FindVertex(segsegIntersect);
if (null == segsegVertex) {
// This happens only for UseSparseVisibilityGraph; in that case we must create the
// intersection vertex in the direction of both segments so we can start walking.
segsegVertex = this.TransUtil.AddVertex(segsegIntersect);
var newEdge = this.AddEdgeToClosestSegmentEnd(scanSeg, segsegVertex, scanSeg.Weight);
this.AddEdgeToClosestSegmentEnd(intSegBefore, segsegVertex, intSegBefore.Weight);
if (PointComparer.Equal(segsegVertex.Point, edgeIntersect)) {
targetVertex = segsegVertex;
return newEdge;
}
}
if (PointComparer.Equal(pointLocation, edgeIntersect)) {
// The initial pointLocation was on scanSeg and we had to create a new vertex for it,
// so we'll find or create (by splitting) the edge on scanSeg that contains pointLocation.
targetVertex = this.TransUtil.FindOrAddVertex(edgeIntersect);
return this.TransUtil.FindOrAddEdge(segsegVertex, targetVertex, weight);
}
targetVertex = null;
return null;
}
private VisibilityEdge AddEdgeToClosestSegmentEnd(ScanSegment scanSeg, VisibilityVertex segsegVertex, double weight) {
// FindOrAddEdge will walk until it finds the minimal bracketing vertices.
if (PointComparer.IsPureLower(scanSeg.HighestVisibilityVertex.Point, segsegVertex.Point)) {
return this.TransUtil.FindOrAddEdge(scanSeg.HighestVisibilityVertex, segsegVertex, weight);
}
if (PointComparer.IsPureLower(segsegVertex.Point, scanSeg.LowestVisibilityVertex.Point)) {
return this.TransUtil.FindOrAddEdge(segsegVertex, scanSeg.LowestVisibilityVertex, weight);
}
return this.TransUtil.FindOrAddEdge(scanSeg.LowestVisibilityVertex, segsegVertex);
}
private void GetPortSpliceLimitRectangle(EdgeGeometry edgeGeom) {
if (!this.LimitPortVisibilitySpliceToEndpointBoundingBox) {
this.portSpliceLimitRectangle = graphGenerator.ObstacleTree.GraphBox;
return;
}
// Return the endpoint-containing rectangle marking the limits of edge-chain extension for a single path.
this.portSpliceLimitRectangle = GetPortRectangle(edgeGeom.SourcePort);
this.portSpliceLimitRectangle.Add(GetPortRectangle(edgeGeom.TargetPort));
if (null != edgeGeom.Waypoints) {
foreach (Point waypoint in edgeGeom.Waypoints) {
AddToLimitRectangle(waypoint);
}
}
}
Rectangle GetPortRectangle(Port port)
{
ObstaclePort oport;
obstaclePortMap.TryGetValue(port, out oport);
if (null != oport) {
#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=369 there are no structs in js
return (oport.Obstacle.VisibilityBoundingBox).Clone();
#else
return (oport.Obstacle.VisibilityBoundingBox);
#endif
}
// FreePoint.
return new Rectangle(ApproximateComparer.Round(port.Location));
}
void AddToLimitRectangle(Point location)
{
if (graphGenerator.IsInBounds(location))
{
this.portSpliceLimitRectangle.Add(location);
}
}
internal IEnumerable FindWaypointVertices(IEnumerable waypoints)
{
// We can't modify EdgeGeometry.Waypoints as the caller owns that, so ApproximateComparer.Round on lookup.
return waypoints.Select(w => this.VisGraph.FindVertex(ApproximateComparer.Round(w)));
}
private FreePoint FindOrCreateFreePoint(Point location) {
FreePoint freePoint;
if (!this.freePointMap.TryGetValue(location, out freePoint)) {
freePoint = new FreePoint(TransUtil, location);
this.freePointMap[location] = freePoint;
} else {
freePoint.GetVertex(TransUtil, location);
}
freePointsInGraph.Insert(freePoint);
freePointLocationsUsedByRouteEdges.Insert(location);
return freePoint;
}
// This is private because it depends on LimitRectangle
private FreePoint AddFreePointToGraph(Point location) {
// This is a FreePoint, either a Waypoint or a Port not in an Obstacle.Ports list.
// We can't modify the Port.Location as the caller owns that, so ApproximateComparer.Round it
// at the point at which we acquire it.
location = ApproximateComparer.Round(location);
// If the point already exists before FreePoint creation, there's nothing to do.
var vertex = VisGraph.FindVertex(location);
var freePoint = this.FindOrCreateFreePoint(location);
if (null != vertex) {
return freePoint;
}
if (!graphGenerator.IsInBounds(location)) {
CreateOutOfBoundsFreePoint(freePoint);
return freePoint;
}
// Vertex is inbounds and does not yet exist. Possibilities are:
// - point is on one ScanSegment (perhaps a dead-end)
// - point is not on any edge (it's in free space so it's in the middle of some rectangle
// (possibly not closed) formed by ScanSegment intersections)
VisibilityEdge edge = null;
freePoint.IsOverlapped = this.ObstacleTree.PointIsInsideAnObstacle(freePoint.Point, HScanSegments.ScanDirection);
var scanSegment = HScanSegments.FindSegmentContainingPoint(location, true /*allowUnfound*/) ??
VScanSegments.FindSegmentContainingPoint(location, true /*allowUnfound*/);
if (null != scanSegment) {
// The location is on one ScanSegment. Find the intersector and split an edge along the segment
// (or extend the VisibilityEdges of the segment in the desired direction).
VisibilityVertex targetVertex;
edge = FindOrCreateNearestPerpEdgeFromNearestPerpSegment(location, scanSegment, location, freePoint.InitialWeight, out targetVertex);
}
Directions edgeDir = Directions.South;
if (null != edge) {
// The freePoint is on one (but not two) segments, and has already been spliced into
// that segment's edge chain. Add edges laterally to the parallel edges.
edgeDir = StaticGraphUtility.EdgeDirection(edge);
ConnectFreePointToLateralEdge(freePoint, CompassVector.RotateLeft(edgeDir));
ConnectFreePointToLateralEdge(freePoint, CompassVector.RotateRight(edgeDir));
}
else {
// The freePoint is not on ScanSegment so we must splice to 4 surrounding edges (or it may be on a
// TransientVE). Look in each of the 4 directions, trying first to avoid crossing any obstacle
// boundaries. However if we cannot find an edge that does not cross an obstacle boundary, the
// freepoint is inside a non-overlapped obstacle, so take a second pass to connect to the nearest
// edge regardless of obstacle boundaries.
for (int ii = 0; ii < 4; ++ii) {
ConnectFreePointToLateralEdge(freePoint, edgeDir);
edgeDir = CompassVector.RotateLeft(edgeDir);
}
}
return freePoint;
}
private void CreateOutOfBoundsFreePoint(FreePoint freePoint) {
// For an out of bounds (OOB) point, we'll link one edge from it to the inbounds edge if it's
// out of bounds in only one direction; if in two, we'll add a bend. Currently we don't need
// to do any more because multiple waypoints are processed as multiple subpaths.
var oobLocation = freePoint.Point;
Point inboundsLocation = graphGenerator.MakeInBoundsLocation(oobLocation);
Directions dirFromGraph = PointComparer.GetDirections(inboundsLocation, oobLocation);
freePoint.OutOfBoundsDirectionFromGraph = dirFromGraph;
if (!PointComparer.IsPureDirection(dirFromGraph)) {
// It's OOB in two directions so will need a bend, but we know inboundsLocation
// is a graph corner so it has a vertex already and we don't need to look up sides.
StaticGraphUtility.Assert(null != VisGraph.FindVertex(inboundsLocation), "graph corner vertex not found", ObstacleTree, VisGraph);
freePoint.AddOobEdgesFromGraphCorner(TransUtil, inboundsLocation);
return;
}
// We know inboundsLocation is on the nearest graphBox border ScanSegment, so this won't return a
// null edge, and we'll just do normal join-to-one-edge handling, extending in the direction to the graph.
var inboundsVertex = this.VisGraph.FindVertex(inboundsLocation);
var dirToGraph = CompassVector.OppositeDir(dirFromGraph);
if (null != inboundsVertex) {
freePoint.AddToAdjacentVertex(this.TransUtil, inboundsVertex, dirToGraph, this.portSpliceLimitRectangle);
}
else {
var edge = this.FindorCreateNearestPerpEdge(oobLocation, inboundsLocation, dirFromGraph, ScanSegment.NormalWeight);
if (null != edge) {
inboundsVertex = freePoint.AddEdgeToAdjacentEdge(this.TransUtil, edge, dirToGraph, this.portSpliceLimitRectangle);
}
}
// This may be an oob waypoint, in which case we want to add additional edges so we can
// go outside graph, cross the waypoint, and come back in. Shortest-paths will do the
// work of determining the optimal path, to avoid backtracking.
var inboundsLeftVertex = StaticGraphUtility.FindNextVertex(inboundsVertex, CompassVector.RotateLeft(dirToGraph));
if (inboundsLeftVertex != null) {
this.TransUtil.ConnectVertexToTargetVertex(freePoint.Vertex, inboundsLeftVertex, dirToGraph, ScanSegment.NormalWeight);
}
var inboundsRightVertex = StaticGraphUtility.FindNextVertex(inboundsVertex, CompassVector.RotateRight(dirToGraph));
if (inboundsRightVertex != null) {
this.TransUtil.ConnectVertexToTargetVertex(freePoint.Vertex, inboundsRightVertex, dirToGraph, ScanSegment.NormalWeight);
}
}
private void ConnectFreePointToLateralEdge(FreePoint freePoint, Directions lateralDir) {
// Turn on pivot vertex to either side to find the next edge to connect to. If the freepoint is
// overlapped (inside an obstacle), just find the closest ScanSegment outside the obstacle and
// start extending from there; otherwise, we can have the FreePoint calculate its max visibility.
var end = freePoint.IsOverlapped ? this.InBoundsGraphBoxIntersect(freePoint.Point, lateralDir)
: freePoint.MaxVisibilityInDirectionForNonOverlappedFreePoint(lateralDir, this.TransUtil);
var lateralEdge = this.FindorCreateNearestPerpEdge(end, freePoint.Point, lateralDir, freePoint.InitialWeight);
// There may be no VisibilityEdge between the current point and any adjoining obstacle in that direction.
if (null != lateralEdge) {
freePoint.AddEdgeToAdjacentEdge(TransUtil, lateralEdge, lateralDir, this.portSpliceLimitRectangle);
}
}
}
}