605 lines
31 KiB
C#
605 lines
31 KiB
C#
|
|
//
|
||
|
|
// SparseVisibilityGraphGenerator.cs
|
||
|
|
// MSAGL base class to create the visibility graph consisting of nlogn ScanSegment intersections for Rectilinear Edge Routing.
|
||
|
|
//
|
||
|
|
// Copyright Microsoft Corporation.
|
||
|
|
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using System.Diagnostics;
|
||
|
|
using System.Linq;
|
||
|
|
using Microsoft.Msagl.Core.DataStructures;
|
||
|
|
using Microsoft.Msagl.Core.Geometry;
|
||
|
|
using Microsoft.Msagl.Routing.Spline.ConeSpanner;
|
||
|
|
|
||
|
|
namespace Microsoft.Msagl.Routing.Rectilinear {
|
||
|
|
// Scan direction is parallel to the sweepline which moves in the perpendicular direction;
|
||
|
|
// i.e. scan direction is "sideways" along the sweepline. We do several passes, following Clarkson et al.,
|
||
|
|
// "Rectilinear shortest paths through polygonal obstacles in O(n (log n)2) time" (checked into the enlistment).
|
||
|
|
// 1. Enumerate all obstacles and load their extreme vertex coordinate projections to the perpendicular axis.
|
||
|
|
// 2. Run a scanline (in each direction) that:
|
||
|
|
// a. Accumulates the vertices and generates obstacle-related Steiner points.
|
||
|
|
// b. Generates the ScanSegments.
|
||
|
|
// 3. Iterate in parallel along the ScanSegments and *VertexPoints to determine the sparse intersections
|
||
|
|
// by binary division, as in the paper.
|
||
|
|
// 4. Finally we create the VisibilityVertices and VisibilityEdges along each ScanSegment from its
|
||
|
|
// list of intersections.
|
||
|
|
// Differences from the paper largely are due to the paper's creation of non-orthogonal edges along
|
||
|
|
// obstacle sides; instead, we create orthogonal edges to the lateral sides of the obstacle's bounding
|
||
|
|
// box. Also, we support overlapped obstacles (interior edges are weighted, as in the non-sparse
|
||
|
|
// implementation) and groups.
|
||
|
|
internal partial class SparseVisibilityGraphGenerator : VisibilityGraphGenerator {
|
||
|
|
/// <summary>
|
||
|
|
/// The points of obstacle vertices encountered on horizontal scan.
|
||
|
|
/// </summary>
|
||
|
|
private readonly Set<Point> horizontalVertexPoints = new Set<Point>();
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// The points of obstacle vertices encountered on vertical scan.
|
||
|
|
/// </summary>
|
||
|
|
private readonly Set<Point> verticalVertexPoints = new Set<Point>();
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// The Steiner points generated at the bounding box of obstacles.
|
||
|
|
/// These help ensure that we can "go around" the obstacle, as with the non-orthogonal edges in the paper.
|
||
|
|
/// </summary>
|
||
|
|
private readonly Set<Point> boundingBoxSteinerPoints = new Set<Point>();
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Accumulates distinct vertex projections to the X axis during sweep.
|
||
|
|
/// </summary>
|
||
|
|
private readonly Set<double> xCoordAccumulator = new Set<double>();
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Accumulates distinct vertex projections to the Y axis during sweep.
|
||
|
|
/// </summary>
|
||
|
|
private readonly Set<double> yCoordAccumulator = new Set<double>();
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// ScanSegment vector locations on the Y axis; final array after sweep.
|
||
|
|
/// </summary>
|
||
|
|
private ScanSegmentVector horizontalScanSegmentVector;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// ScanSegment vector locations on the X axis; final array after sweep.
|
||
|
|
/// </summary>
|
||
|
|
private ScanSegmentVector verticalScanSegmentVector;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// The index from a coordinate to a horizontal vector slot.
|
||
|
|
/// </summary>
|
||
|
|
private readonly Dictionary<double, int> horizontalCoordMap = new Dictionary<double, int>();
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// The index from a point to a vertical vector slot.
|
||
|
|
/// </summary>
|
||
|
|
private readonly Dictionary<double, int> verticalCoordMap = new Dictionary<double, int>();
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// The index from a coordinate to a vector slot on the axis we are intersecting to.
|
||
|
|
/// </summary>
|
||
|
|
private Dictionary<double, int> perpendicularCoordMap;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// The segment vector we are intersecting along.
|
||
|
|
/// </summary>
|
||
|
|
private ScanSegmentVector parallelSegmentVector;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// The segment vector we are intersecting to.
|
||
|
|
/// </summary>
|
||
|
|
private ScanSegmentVector perpendicularSegmentVector;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// The comparer for points along the horizontal or vertical axis.
|
||
|
|
/// </summary>
|
||
|
|
private IComparer<Point> currentAxisPointComparer;
|
||
|
|
|
||
|
|
internal SparseVisibilityGraphGenerator()
|
||
|
|
: base(wantReflections: false) {
|
||
|
|
}
|
||
|
|
|
||
|
|
internal override void Clear() {
|
||
|
|
base.Clear();
|
||
|
|
this.Cleanup();
|
||
|
|
}
|
||
|
|
|
||
|
|
private void Cleanup() {
|
||
|
|
this.horizontalVertexPoints.Clear();
|
||
|
|
this.verticalVertexPoints.Clear();
|
||
|
|
this.boundingBoxSteinerPoints.Clear();
|
||
|
|
this.xCoordAccumulator.Clear();
|
||
|
|
this.yCoordAccumulator.Clear();
|
||
|
|
this.horizontalCoordMap.Clear();
|
||
|
|
this.verticalCoordMap.Clear();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Generate the visibility graph along which edges will be routed.
|
||
|
|
/// </summary>
|
||
|
|
/// <returns></returns>
|
||
|
|
internal override void GenerateVisibilityGraph() {
|
||
|
|
this.AccumulateVertexCoords();
|
||
|
|
this.CreateSegmentVectorsAndPopulateCoordinateMaps();
|
||
|
|
this.RunScanLineToCreateSegmentsAndBoundingBoxSteinerPoints();
|
||
|
|
this.GenerateSparseIntersectionsFromVertexPoints();
|
||
|
|
this.CreateScanSegmentTrees();
|
||
|
|
|
||
|
|
HorizontalScanSegments.DevTraceVerifyVisibility();
|
||
|
|
VerticalScanSegments.DevTraceVerifyVisibility();
|
||
|
|
Debug_AssertGraphIsRectilinear(VisibilityGraph, ObstacleTree);
|
||
|
|
|
||
|
|
this.Cleanup();
|
||
|
|
}
|
||
|
|
|
||
|
|
void AccumulateVertexCoords() {
|
||
|
|
// Unlike the paper we only generate lines for extreme vertices (i.e. on the horizontal pass we
|
||
|
|
// don't generate a horizontal vertex projection to the Y axis for a vertex that is not on the top
|
||
|
|
// or bottom of the obstacle). So we can just use the bounding box.
|
||
|
|
foreach (var obstacle in this.ObstacleTree.GetAllObstacles()) {
|
||
|
|
xCoordAccumulator.Insert(obstacle.VisibilityBoundingBox.Left);
|
||
|
|
xCoordAccumulator.Insert(obstacle.VisibilityBoundingBox.Right);
|
||
|
|
yCoordAccumulator.Insert(obstacle.VisibilityBoundingBox.Top);
|
||
|
|
yCoordAccumulator.Insert(obstacle.VisibilityBoundingBox.Bottom);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void CreateSegmentVectorsAndPopulateCoordinateMaps() {
|
||
|
|
this.horizontalScanSegmentVector = new ScanSegmentVector(yCoordAccumulator, true);
|
||
|
|
this.verticalScanSegmentVector = new ScanSegmentVector(xCoordAccumulator, false);
|
||
|
|
|
||
|
|
for (int slot = 0; slot < this.horizontalScanSegmentVector.Length; ++slot) {
|
||
|
|
this.horizontalCoordMap[this.horizontalScanSegmentVector[slot].Coord] = slot;
|
||
|
|
}
|
||
|
|
for (int slot = 0; slot < this.verticalScanSegmentVector.Length; ++slot) {
|
||
|
|
this.verticalCoordMap[this.verticalScanSegmentVector[slot].Coord] = slot;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void RunScanLineToCreateSegmentsAndBoundingBoxSteinerPoints() {
|
||
|
|
// Do a scanline pass to create scan segments that span the entire height/width of the graph
|
||
|
|
// (mixing overlapped with free segments as needed) and generate the type-2 Steiner points.
|
||
|
|
base.GenerateVisibilityGraph();
|
||
|
|
this.horizontalScanSegmentVector.ScanSegmentsComplete();
|
||
|
|
this.verticalScanSegmentVector.ScanSegmentsComplete();
|
||
|
|
this.xCoordAccumulator.Clear();
|
||
|
|
this.yCoordAccumulator.Clear();
|
||
|
|
}
|
||
|
|
|
||
|
|
internal override void InitializeEventQueue(ScanDirection scanDir)
|
||
|
|
{
|
||
|
|
base.InitializeEventQueue(scanDir);
|
||
|
|
this.SetVectorsAndCoordMaps(scanDir);
|
||
|
|
this.AddAxisCoordinateEvents(scanDir);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void AddAxisCoordinateEvents(ScanDirection scanDir) {
|
||
|
|
// Normal event ordering will apply - and will thus order the ScanSegments created in the vectors.
|
||
|
|
if (scanDir.IsHorizontal) {
|
||
|
|
foreach (var coord in yCoordAccumulator) {
|
||
|
|
base.eventQueue.Enqueue(new AxisCoordinateEvent(new Point(ObstacleTree.GraphBox.Left - SentinelOffset, coord)));
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
foreach (var coord in xCoordAccumulator) {
|
||
|
|
base.eventQueue.Enqueue(new AxisCoordinateEvent(new Point(coord, ObstacleTree.GraphBox.Bottom - SentinelOffset)));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override void ProcessCustomEvent(SweepEvent evt) {
|
||
|
|
if (!ProcessAxisCoordinate(evt)) {
|
||
|
|
base.ProcessCustomEvent(evt);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private bool ProcessAxisCoordinate(SweepEvent evt) {
|
||
|
|
var axisEvent = evt as AxisCoordinateEvent;
|
||
|
|
if (null != axisEvent) {
|
||
|
|
this.CreateScanSegmentsOnAxisCoordinate(axisEvent.Site);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override bool InsertPerpendicularReflectionSegment(Point start, Point end) {
|
||
|
|
Debug.Assert(false, "base.wantReflections is false in Sparse mode so this should never be called");
|
||
|
|
// ReSharper disable HeuristicUnreachableCode
|
||
|
|
return false;
|
||
|
|
// ReSharper restore HeuristicUnreachableCode
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override bool InsertParallelReflectionSegment(Point start, Point end, Obstacle eventObstacle,
|
||
|
|
BasicObstacleSide lowNborSide, BasicObstacleSide highNborSide, BasicReflectionEvent action) {
|
||
|
|
Debug.Assert(false, "base.wantReflections is false in Sparse mode so this should never be called");
|
||
|
|
// ReSharper disable HeuristicUnreachableCode
|
||
|
|
return false;
|
||
|
|
// ReSharper restore HeuristicUnreachableCode
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override void ProcessVertexEvent(RBNode<BasicObstacleSide> lowSideNode,
|
||
|
|
RBNode<BasicObstacleSide> highSideNode, BasicVertexEvent vertexEvent) {
|
||
|
|
var vertexPoints = (base.ScanDirection.IsHorizontal) ? this.horizontalVertexPoints : this.verticalVertexPoints;
|
||
|
|
vertexPoints.Insert(vertexEvent.Site);
|
||
|
|
|
||
|
|
// For easier reading...
|
||
|
|
var lowNborSide = LowNeighborSides.LowNeighbor.Item;
|
||
|
|
var highNborSide = HighNeighborSides.HighNeighbor.Item;
|
||
|
|
var highDir = base.ScanDirection.Direction;
|
||
|
|
var lowDir = base.ScanDirection.OppositeDirection;
|
||
|
|
|
||
|
|
// Generate the neighbor side intersections, regardless of overlaps; these are the type-2 Steiner points.
|
||
|
|
var lowSteiner = ScanLineIntersectSide(vertexEvent.Site, lowNborSide);
|
||
|
|
var highSteiner = ScanLineIntersectSide(vertexEvent.Site, highNborSide);
|
||
|
|
|
||
|
|
// Add the intersections at the neighbor bounding boxes if the intersection is not at a sentinel.
|
||
|
|
// Go in the opposite direction from the neighbor intersection to find the border between the Steiner
|
||
|
|
// point and vertexEvent.Site (unless vertexEvent.Site is inside the bounding box).
|
||
|
|
if (ObstacleTree.GraphBox.Contains(lowSteiner)) {
|
||
|
|
var bboxIntersectBeforeLowSteiner = StaticGraphUtility.RectangleBorderIntersect(lowNborSide.Obstacle.VisibilityBoundingBox, lowSteiner, highDir);
|
||
|
|
if (PointComparer.IsPureLower(bboxIntersectBeforeLowSteiner, vertexEvent.Site)) {
|
||
|
|
this.boundingBoxSteinerPoints.Insert(bboxIntersectBeforeLowSteiner);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (ObstacleTree.GraphBox.Contains(highSteiner)) {
|
||
|
|
var bboxIntersectBeforeHighSteiner = StaticGraphUtility.RectangleBorderIntersect(highNborSide.Obstacle.VisibilityBoundingBox, highSteiner, lowDir);
|
||
|
|
if (PointComparer.IsPureLower(vertexEvent.Site, bboxIntersectBeforeHighSteiner)) {
|
||
|
|
this.boundingBoxSteinerPoints.Insert(bboxIntersectBeforeHighSteiner);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add the corners of the bounding box of the vertex obstacle, if they are visible to the event site.
|
||
|
|
// This ensures that we "go around" the obstacle, as with the non-orthogonal edges in the paper.
|
||
|
|
Point lowCorner, highCorner;
|
||
|
|
GetBoundingCorners(lowSideNode.Item.Obstacle.VisibilityBoundingBox, vertexEvent is OpenVertexEvent, this.ScanDirection.IsHorizontal,
|
||
|
|
out lowCorner, out highCorner);
|
||
|
|
if (PointComparer.IsPureLower(lowSteiner, lowCorner) || lowNborSide.Obstacle.IsInSameClump(vertexEvent.Obstacle)) {
|
||
|
|
vertexPoints.Insert(lowCorner);
|
||
|
|
}
|
||
|
|
if (PointComparer.IsPureLower(highCorner, highSteiner) || highNborSide.Obstacle.IsInSameClump(vertexEvent.Obstacle)) {
|
||
|
|
vertexPoints.Insert(highCorner);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void GetBoundingCorners(Rectangle boundingBox, bool isLowSide, bool isHorizontal, out Point lowCorner, out Point highCorner) {
|
||
|
|
if (isLowSide) {
|
||
|
|
lowCorner = boundingBox.LeftBottom;
|
||
|
|
highCorner = isHorizontal ? boundingBox.RightBottom : boundingBox.LeftTop;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
lowCorner = isHorizontal ? boundingBox.LeftTop : boundingBox.RightBottom;
|
||
|
|
highCorner = boundingBox.RightTop;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void CreateScanSegmentsOnAxisCoordinate(Point site) {
|
||
|
|
base.CurrentGroupBoundaryCrossingMap.Clear();
|
||
|
|
|
||
|
|
// Iterate the ScanLine and create ScanSegments. There will always be at least the two sentinel sides.
|
||
|
|
var sideNode = base.scanLine.Lowest();
|
||
|
|
var nextNode = base.scanLine.NextHigh(sideNode);
|
||
|
|
var overlapDepth = 0;
|
||
|
|
var start = site;
|
||
|
|
bool isInsideOverlappedObstacle = false;
|
||
|
|
for (; null != nextNode; nextNode = base.scanLine.NextHigh(nextNode)) {
|
||
|
|
if (SkipSide(start, nextNode.Item)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (nextNode.Item.Obstacle.IsGroup) {
|
||
|
|
// Do not create internal group crossings in non-overlapped obstacles.
|
||
|
|
if ((overlapDepth == 0) || isInsideOverlappedObstacle) {
|
||
|
|
HandleGroupCrossing(site, nextNode.Item);
|
||
|
|
}
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
var isLowSide = nextNode.Item is LowObstacleSide;
|
||
|
|
if (isLowSide) {
|
||
|
|
if (overlapDepth > 0) {
|
||
|
|
++overlapDepth;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// We are not overlapped, so create a ScanSegment from the previous side intersection to the
|
||
|
|
// intersection with the side in nextNode.Item.
|
||
|
|
start = CreateScanSegment(start, nextNode.Item, ScanSegment.NormalWeight);
|
||
|
|
base.CurrentGroupBoundaryCrossingMap.Clear();
|
||
|
|
overlapDepth = 1;
|
||
|
|
isInsideOverlappedObstacle = nextNode.Item.Obstacle.IsOverlapped;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// This is a HighObstacleSide. If we've got overlap nesting, decrement the depth.
|
||
|
|
Debug.Assert(overlapDepth > 0, "Overlap depth must be positive");
|
||
|
|
--overlapDepth;
|
||
|
|
if (overlapDepth > 0) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// If we are not within an overlapped obstacle, don't bother creating the overlapped ScanSegment
|
||
|
|
// as there will never be visibility connecting to it.
|
||
|
|
start = (nextNode.Item.Obstacle.IsOverlapped || nextNode.Item.Obstacle.OverlapsGroupCorner)
|
||
|
|
? this.CreateScanSegment(start, nextNode.Item, ScanSegment.OverlappedWeight)
|
||
|
|
: this.ScanLineIntersectSide(start, nextNode.Item);
|
||
|
|
base.CurrentGroupBoundaryCrossingMap.Clear();
|
||
|
|
isInsideOverlappedObstacle = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// The final piece.
|
||
|
|
var end = base.ScanDirection.IsHorizontal
|
||
|
|
? new Point(ObstacleTree.GraphBox.Right + SentinelOffset, start.Y)
|
||
|
|
: new Point(start.X, ObstacleTree.GraphBox.Top + SentinelOffset);
|
||
|
|
this.parallelSegmentVector.CreateScanSegment(start, end, ScanSegment.NormalWeight,
|
||
|
|
base.CurrentGroupBoundaryCrossingMap.GetOrderedListBetween(start, end));
|
||
|
|
this.parallelSegmentVector.ScanSegmentsCompleteForCurrentSlot();
|
||
|
|
}
|
||
|
|
|
||
|
|
private void HandleGroupCrossing(Point site, BasicObstacleSide groupSide) {
|
||
|
|
if (!base.ScanLineCrossesObstacle(site, groupSide.Obstacle)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
// Here we are always going left-to-right. As in base.SkipToNeighbor, we don't stop traversal for groups,
|
||
|
|
// neither do we create overlapped edges (unless we're inside a non-group obstacle). Instead we turn
|
||
|
|
// the boundary crossing on or off based on group membership at ShortestPath-time. Even though this is
|
||
|
|
// the sparse VG, we always create these edges at group boundaries so we don't skip over them.
|
||
|
|
Directions dirToInsideOfGroup = (groupSide is LowObstacleSide) ? base.ScanDirection.Direction : base.ScanDirection.OppositeDirection;
|
||
|
|
var intersect = this.ScanLineIntersectSide(site, groupSide);
|
||
|
|
var crossing = base.CurrentGroupBoundaryCrossingMap.AddIntersection(intersect, groupSide.Obstacle, dirToInsideOfGroup);
|
||
|
|
|
||
|
|
// The vertex crossing the edge is perpendicular to the group boundary. A rectilinear group will also have
|
||
|
|
// an edge parallel to that group boundary that includes the point of that crossing vertex; therefore we must
|
||
|
|
// split that non-crossing edge at that vertex.
|
||
|
|
AddPerpendicularCoordForGroupCrossing(intersect);
|
||
|
|
|
||
|
|
// Similarly, the crossing edge's opposite vertex may be on a perpendicular segment.
|
||
|
|
var interiorPoint = crossing.GetInteriorVertexPoint(intersect);
|
||
|
|
AddPerpendicularCoordForGroupCrossing(interiorPoint);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void AddPerpendicularCoordForGroupCrossing(Point intersect) {
|
||
|
|
var nonCrossingPerpSlot = this.FindPerpendicularSlot(intersect, 0);
|
||
|
|
if (-1 != nonCrossingPerpSlot) {
|
||
|
|
this.perpendicularSegmentVector[nonCrossingPerpSlot].AddPendingPerpendicularCoord(this.parallelSegmentVector.CurrentSlot.Coord);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private bool SkipSide(Point start, BasicObstacleSide side) {
|
||
|
|
if (side.Obstacle.IsSentinel) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Skip sides of obstacles that we do not actually pass through.
|
||
|
|
var bbox = side.Obstacle.VisibilityBoundingBox;
|
||
|
|
if (base.ScanDirection.IsHorizontal) {
|
||
|
|
return ((start.Y == bbox.Bottom) || (start.Y == bbox.Top));
|
||
|
|
}
|
||
|
|
return ((start.X == bbox.Left) || (start.X == bbox.Right));
|
||
|
|
}
|
||
|
|
|
||
|
|
private Point CreateScanSegment(Point start, BasicObstacleSide side, double weight) {
|
||
|
|
var end = ScanLineIntersectSide(start, side);
|
||
|
|
if (start != end) {
|
||
|
|
this.parallelSegmentVector.CreateScanSegment(start, end, weight, CurrentGroupBoundaryCrossingMap.GetOrderedListBetween(start, end));
|
||
|
|
}
|
||
|
|
return end;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void GenerateSparseIntersectionsFromVertexPoints() {
|
||
|
|
this.VisibilityGraph = NewVisibilityGraph();
|
||
|
|
|
||
|
|
// Generate the sparse intersections between ScanSegments based upon the ordered vertexPoints.
|
||
|
|
GenerateSparseIntersectionsAlongHorizontalAxis();
|
||
|
|
GenerateSparseIntersectionsAlongVerticalAxis();
|
||
|
|
|
||
|
|
this.ConnectAdjoiningScanSegments();
|
||
|
|
|
||
|
|
// Now each segment has the coordinates all of its intersections, so create the visibility graph.
|
||
|
|
this.horizontalScanSegmentVector.CreateSparseVerticesAndEdges(this.VisibilityGraph);
|
||
|
|
this.verticalScanSegmentVector.CreateSparseVerticesAndEdges(this.VisibilityGraph);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void GenerateSparseIntersectionsAlongHorizontalAxis() {
|
||
|
|
this.currentAxisPointComparer = new HorizontalPointComparer();
|
||
|
|
var vertexPoints = this.horizontalVertexPoints.OrderBy(point => point, this.currentAxisPointComparer).GetEnumerator();
|
||
|
|
var bboxSteinerPoints = new EnumeratorWrapper<Point>(this.boundingBoxSteinerPoints.OrderBy(point => point, this.currentAxisPointComparer));
|
||
|
|
base.ScanDirection = ScanDirection.HorizontalInstance;
|
||
|
|
SetVectorsAndCoordMaps(base.ScanDirection);
|
||
|
|
this.GenerateSparseIntersections(vertexPoints, bboxSteinerPoints);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void GenerateSparseIntersectionsAlongVerticalAxis() {
|
||
|
|
this.currentAxisPointComparer = new VerticalPointComparer();
|
||
|
|
var vertexPoints = this.verticalVertexPoints.OrderBy(point => point, this.currentAxisPointComparer).GetEnumerator();
|
||
|
|
var bboxSteinerPoints = new EnumeratorWrapper<Point>(this.boundingBoxSteinerPoints.OrderBy(point => point, this.currentAxisPointComparer));
|
||
|
|
base.ScanDirection = ScanDirection.VerticalInstance;
|
||
|
|
SetVectorsAndCoordMaps(base.ScanDirection);
|
||
|
|
this.GenerateSparseIntersections(vertexPoints, bboxSteinerPoints);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void SetVectorsAndCoordMaps(ScanDirection scanDir) {
|
||
|
|
if (scanDir.IsHorizontal) {
|
||
|
|
this.parallelSegmentVector = this.horizontalScanSegmentVector;
|
||
|
|
this.perpendicularSegmentVector = this.verticalScanSegmentVector;
|
||
|
|
this.perpendicularCoordMap = this.verticalCoordMap;
|
||
|
|
} else {
|
||
|
|
this.parallelSegmentVector = this.verticalScanSegmentVector;
|
||
|
|
this.perpendicularSegmentVector = this.horizontalScanSegmentVector;
|
||
|
|
this.perpendicularCoordMap = this.horizontalCoordMap;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void ConnectAdjoiningScanSegments() {
|
||
|
|
// Ensure there is a vertex at the end/start point of two ScanSegments; these will always differ in overlappedness.
|
||
|
|
this.horizontalScanSegmentVector.ConnectAdjoiningSegmentEndpoints();
|
||
|
|
this.verticalScanSegmentVector.ConnectAdjoiningSegmentEndpoints();
|
||
|
|
}
|
||
|
|
|
||
|
|
internal class HorizontalPointComparer : IComparer<Point> {
|
||
|
|
// Order by vertical first, then horizontal.
|
||
|
|
public int Compare(Point lhs, Point rhs) {
|
||
|
|
var cmp = lhs.Y.CompareTo(rhs.Y);
|
||
|
|
return (0 != cmp) ? cmp : lhs.X.CompareTo(rhs.X);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public class VerticalPointComparer : IComparer<Point> {
|
||
|
|
// Order by horizontal first, then vertical.
|
||
|
|
public int Compare(Point lhs, Point rhs) {
|
||
|
|
var cmp = lhs.X.CompareTo(rhs.X);
|
||
|
|
return (0 != cmp) ? cmp : lhs.Y.CompareTo(rhs.Y);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void GenerateSparseIntersections(IEnumerator<Point> vertexPoints, EnumeratorWrapper<Point> bboxSteinerPoints) {
|
||
|
|
this.perpendicularSegmentVector.ResetForIntersections();
|
||
|
|
this.parallelSegmentVector.ResetForIntersections();
|
||
|
|
|
||
|
|
// Position the enumerations to the first point.
|
||
|
|
vertexPoints.MoveNext();
|
||
|
|
bboxSteinerPoints.MoveNext();
|
||
|
|
foreach (var item in parallelSegmentVector.Items) {
|
||
|
|
for (;;) {
|
||
|
|
if (!item.CurrentSegment.ContainsPoint(vertexPoints.Current)) {
|
||
|
|
// Done accumulating intersections for the current segment; move to the next segment.
|
||
|
|
if (!this.AddSteinerPointsToInterveningSegments(vertexPoints.Current, bboxSteinerPoints, item)
|
||
|
|
|| !item.TraverseToSegmentContainingPoint(vertexPoints.Current)) {
|
||
|
|
// Done with this vectorItem, move to the next item.
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
this.AddPointsToCurrentSegmentIntersections(bboxSteinerPoints, item);
|
||
|
|
this.GenerateIntersectionsFromVertexPointForCurrentSegment(vertexPoints.Current, item);
|
||
|
|
|
||
|
|
if (item.PointIsCurrentEndAndNextStart(vertexPoints.Current)) {
|
||
|
|
// MoveNext will always return true because the test to enter this block returned true.
|
||
|
|
item.MoveNext();
|
||
|
|
Debug.Assert(item.HasCurrent, "MoveNext ended before EndAndNextStart");
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!vertexPoints.MoveNext()) {
|
||
|
|
// No more vertexPoints; we're done.
|
||
|
|
Debug.Assert(!bboxSteinerPoints.HasCurrent, "Some boundingBoxSteinerPoints remain");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// We should have exited in the "no more vertexPoints" case above.
|
||
|
|
Debug.Assert(false, "Mismatch in points and segments");
|
||
|
|
}
|
||
|
|
|
||
|
|
private bool AddSteinerPointsToInterveningSegments(Point currentVertexPoint, EnumeratorWrapper<Point> bboxSteinerPoints, ScanSegmentVectorItem item) {
|
||
|
|
// With overlaps, we may have bboxSteinerPoints on segments that do not contain vertices.
|
||
|
|
while (bboxSteinerPoints.HasCurrent && (this.currentAxisPointComparer.Compare(bboxSteinerPoints.Current, currentVertexPoint) == -1)) {
|
||
|
|
if (!item.TraverseToSegmentContainingPoint(bboxSteinerPoints.Current)) {
|
||
|
|
// Done with this vectorItem, move to the next item.
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
this.AddPointsToCurrentSegmentIntersections(bboxSteinerPoints, item);
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void AddPointsToCurrentSegmentIntersections(EnumeratorWrapper<Point> pointsToAdd, ScanSegmentVectorItem parallelItem) {
|
||
|
|
// The first Steiner point should be in the segment, unless we have a non-orthogonal or overlapped or both situation
|
||
|
|
// that results in no Steiner points having been generated, or Steiner points being generated on a segment that has
|
||
|
|
// the opposite overlap state from the segment containing the corresponding vertex.
|
||
|
|
for (; pointsToAdd.HasCurrent && parallelItem.CurrentSegment.ContainsPoint(pointsToAdd.Current); pointsToAdd.MoveNext()) {
|
||
|
|
int steinerSlot = this.FindPerpendicularSlot(pointsToAdd.Current, 0);
|
||
|
|
this.AddSlotToSegmentIntersections(parallelItem, steinerSlot);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void GenerateIntersectionsFromVertexPointForCurrentSegment(Point site, ScanSegmentVectorItem parallelItem) {
|
||
|
|
int perpStartSlot = this.FindPerpendicularSlot(parallelItem.CurrentSegment.Start, 1);
|
||
|
|
int perpEndSlot = this.FindPerpendicularSlot(parallelItem.CurrentSegment.End, -1);
|
||
|
|
int siteSlot = this.FindPerpendicularSlot(site, 0);
|
||
|
|
|
||
|
|
// See comments in FindIntersectingSlot; we don't add non-extreme vertices in the perpendicular direction
|
||
|
|
// so in some heavily-overlapped scenarios, we may not have any intersections within this scan segment.
|
||
|
|
if (perpStartSlot >= perpEndSlot) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.AddSlotToSegmentIntersections(parallelItem, perpStartSlot);
|
||
|
|
this.AddSlotToSegmentIntersections(parallelItem, perpEndSlot);
|
||
|
|
if ((siteSlot > perpStartSlot) && (siteSlot < perpEndSlot)) {
|
||
|
|
this.AddSlotToSegmentIntersections(parallelItem, siteSlot);
|
||
|
|
this.AddBinaryDivisionSlotsToSegmentIntersections(parallelItem, perpStartSlot, siteSlot, perpEndSlot);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// These are called when the site may not be in the vector.
|
||
|
|
private int FindPerpendicularSlot(Point site, int directionIfMiss) {
|
||
|
|
return FindIntersectingSlot(perpendicularSegmentVector, perpendicularCoordMap, site, directionIfMiss);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static int FindIntersectingSlot(ScanSegmentVector segmentVector, Dictionary<double, int> coordMap, Point site, int directionIfMiss) {
|
||
|
|
var coord = segmentVector.GetParallelCoord(site);
|
||
|
|
int slot;
|
||
|
|
if (coordMap.TryGetValue(coord, out slot)) {
|
||
|
|
return slot;
|
||
|
|
}
|
||
|
|
|
||
|
|
// There are a few cases where the perpCoord is not in the map:
|
||
|
|
// 1. The first ScanSegment in a slot will have a Start at the sentinel, which is before the first
|
||
|
|
// perpendicular segment; similarly, the last ScanSegment in a slot will have an out-of-range End.
|
||
|
|
// 2. Sequences of overlapped/nonoverlapped scan segments that pass through obstacles. Their start
|
||
|
|
// and end points are not in vertexPoints because they were not vertex-derived, so we find the
|
||
|
|
// closest bracketing coordinates that are in the vectors.
|
||
|
|
// 3. Non-extreme vertices in the perpendicular direction (e.g. for a triangle, we add the X's of
|
||
|
|
// the left and right to the coords, but not of the top).
|
||
|
|
// 4. Non-rectilinear group side intersections.
|
||
|
|
return (0 == directionIfMiss) ? -1 : segmentVector.FindNearest(coord, directionIfMiss);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void AddSlotToSegmentIntersections(ScanSegmentVectorItem parallelItem, int perpSlot) {
|
||
|
|
ScanSegmentVectorItem perpItem = this.perpendicularSegmentVector[perpSlot];
|
||
|
|
parallelItem.CurrentSegment.AddSparseVertexCoord(perpItem.Coord);
|
||
|
|
perpItem.AddPerpendicularCoord(parallelItem.Coord);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void AddBinaryDivisionSlotsToSegmentIntersections(ScanSegmentVectorItem parallelItem, int startSlot, int siteSlot, int endSlot) {
|
||
|
|
// The input parameters' slots have already been added to the segment's coords.
|
||
|
|
// If there was no object to the low or high side, then the start or end slot was already
|
||
|
|
// the graphbox max (0 or perpSegmentVector.Length, respectively). So start dividing.
|
||
|
|
int low = 0;
|
||
|
|
int high = this.perpendicularSegmentVector.Length - 1;
|
||
|
|
|
||
|
|
// Terminate when we are one away because we don't have an edge from a point to itself.
|
||
|
|
while ((high - low) > 1) {
|
||
|
|
int mid = low + ((high - low) / 2);
|
||
|
|
|
||
|
|
// We only use the half of the graph that the site is in, so arbitrarily decide that it is
|
||
|
|
// in the lower half if it is at the midpoint.
|
||
|
|
if (siteSlot <= mid) {
|
||
|
|
high = mid;
|
||
|
|
if ((siteSlot < high) && (high <= endSlot)) {
|
||
|
|
this.AddSlotToSegmentIntersections(parallelItem, high);
|
||
|
|
}
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
low = mid;
|
||
|
|
if ((siteSlot > low) && (low >= startSlot)) {
|
||
|
|
this.AddSlotToSegmentIntersections(parallelItem, low);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create the ScanSegmentTrees that functions as indexes for port-visibility splicing.
|
||
|
|
private void CreateScanSegmentTrees() {
|
||
|
|
CreateScanSegmentTree(this.horizontalScanSegmentVector, this.HorizontalScanSegments);
|
||
|
|
CreateScanSegmentTree(this.verticalScanSegmentVector, this.VerticalScanSegments);
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void CreateScanSegmentTree(ScanSegmentVector segmentVector, ScanSegmentTree segmentTree) {
|
||
|
|
foreach (var item in segmentVector.Items) {
|
||
|
|
for (var segment = item.FirstSegment; segment != null; segment = segment.NextSegment) {
|
||
|
|
if (segment.HasVisibility()) {
|
||
|
|
segmentTree.InsertUnique(segment);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|