Aetheria/Assets/Plugins/MSAGL/Routing/Rectilinear/VisibilityGraphGenerator.cs

1010 lines
61 KiB
C#
Raw Normal View History

//
// VisibilityGraphGenerator.cs
// MSAGL base class to create the visibility graph for Rectilinear Edge Routing.
//
// Copyright Microsoft Corporation.
using System;
using System.Diagnostics;
using Microsoft.Msagl.Core.DataStructures;
using Microsoft.Msagl.Core.Geometry;
using Microsoft.Msagl.Core.Geometry.Curves;
using Microsoft.Msagl.Routing.Spline.ConeSpanner;
using Microsoft.Msagl.Routing.Visibility;
#if DEBUG
using System.Linq;
using Microsoft.Msagl.DebugHelpers;
#endif
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 also have lookahead scans
// that enqueue events along the scan-primary coordinate (in the direction of the scan, i.e.
// X for Hscan, Y for Vscan) to handle reflections from non-orthogonal obstacle sides,
// and lookback scans that have not had their reflections calculated because they reflect
// backward from the scanline and thus must be picked up on a subsequent perpendicular sweep.
internal abstract partial class VisibilityGraphGenerator {
// The direction of the current scan - perpendicular to the sweep direction
// (i.e. a vertical sweep will scan along a horizontal line).
protected ScanDirection ScanDirection;
/// <summary>
/// The visibility graph generated by GenerateVisibilityGraph.
/// </summary>
internal VisibilityGraph VisibilityGraph { get; set; }
protected VisibilityGraphGenerator(bool wantReflections) {
this.ScanDirection = ScanDirection.HorizontalInstance;
this.eventQueue = new EventQueue();
this.HorizontalScanSegments = new ScanSegmentTree(ScanDirection.HorizontalInstance);
this.VerticalScanSegments = new ScanSegmentTree(ScanDirection.VerticalInstance);
this.wantReflections = wantReflections;
}
// The events we generate and process during the scan.
protected EventQueue eventQueue;
// The line segments produced during the vertex event processing.
internal ScanSegmentTree HorizontalScanSegments;
internal ScanSegmentTree VerticalScanSegments;
protected ScanSegmentTree ParallelScanSegments // Parallel to scanline
{ get { return ScanDirection.IsHorizontal ? HorizontalScanSegments : VerticalScanSegments; } }
protected ScanSegmentTree PerpendicularScanSegments // Perpendicular to scanline
{ get { return ScanDirection.IsHorizontal ? VerticalScanSegments : HorizontalScanSegments; } }
// For lookahead points, we record the point of the intersection on the reflecting side, then
// whenever we load a side, we check for active lookahead lines within this range. Since we
// are just intersecting rays, we only care about the X (H scan) or Y (V scan) coordinate.
LookaheadScan lookaheadScan;
private readonly bool wantReflections;
// This is the tree of rectangle nodes of the input obstacles' padded encompassing polylines.
internal ObstacleTree ObstacleTree = new ObstacleTree();
// The scanline of obstacle sides, for processing events.
protected RectilinearScanLine scanLine;
// This ensures that sentinels lie outside the graph boundaries, to ensure the scanline orders
// orders the sentinels properly. Its borders are not drawn; instead the borders of objects
// already contain padding and this is assumed to be sufficient to provide space for routing.
internal const double SentinelOffset = 1.0;
// This is a map of all group boundary crossings for the current scanline event being processed,
// including the direction (along the scanline axis, horizontal or vertical) that a path crossing
// that boundary takes toward the group's interior. A point may be in here twice in the event of
// groups sharing a boundary.
protected readonly GroupBoundaryCrossingMap CurrentGroupBoundaryCrossingMap = new GroupBoundaryCrossingMap();
// For scanline traversal.
protected readonly NeighborSides LowNeighborSides = new NeighborSides();
protected readonly NeighborSides HighNeighborSides = new NeighborSides();
internal static VisibilityGraph NewVisibilityGraph() {
return new VisibilityGraph { VertexFactory = (point => new VisibilityVertexRectilinear(point)) };
}
/// <summary>
/// Generate the visibility graph along which edges will be routed.
/// </summary>
/// <returns></returns>
internal virtual void GenerateVisibilityGraph() {
// Generate the Polyline tree from the padded shapes. ObstacleTree allows us to do
// obstacle hit-testing for dynamic obstacles, as well as providing input for the Nudger.
// Each PolylinePoint contains a reference to the Polyline of which it is a member.
if (null == ObstacleTree.Root) {
return;
}
// Now enqueue initial events for the vertical sweep (horizontal scan); start with the lowest
// vertices and then we'll load subsequent vertices (and intersections) as we encounter them.
// We'll defer the generation of vertex events in the perpendicular direction until we're done
// with this sweep, to save memory; but we may enqueue intersection events in the perpendicular
// direction during this sweep.
InitializeEventQueue(ScanDirection.HorizontalInstance);
// For the first sweep, EnqueueBottomVertexEvents calculates our min/max boundaries.
// Validate them here; we need to have enough room to create the sentinels. We need
// only the Padding of the adjacent obstacle(s) for correct line spacing outside these
// extreme obstacles, so don't need to pad the sentinels.
if ((ObstacleTree.GraphBox.Left <= (Double.MinValue + SentinelOffset))
|| (ObstacleTree.GraphBox.Bottom <= (Double.MinValue + SentinelOffset))
|| (ObstacleTree.GraphBox.Right >= (Double.MaxValue - SentinelOffset))
|| (ObstacleTree.GraphBox.Top >= (Double.MaxValue - SentinelOffset))
) {
throw new InvalidOperationException("One or more obstacle boundaries are out of range");
}
// Create the sentinels and add them to the scanline. Do NOT add them to the event queue
// because we're not going to close them. We're also effectively adding only the inner side
// perpendicular to the scan, but we need the polyline for segment-subsumption tracking.
// Creating with two points has the lines "clockwise" (and counterclockwise) as above.
//
// We don't need to store the sentinels as we retrieve them from [SweepEvent].Vertex.Polyline.
// And we only need the low sentine's HighSide and the high sentinel's LowSide, but we use both
// to save Obstacle from having to worry about the special case. Note: reflection will
// automatically not happen when hitting sentinels because their borders are IsPerpendicular.
int scanlineSentinelOrdinal = Obstacle.FirstSentinelOrdinal;
// Low sentinel...
var lowerCorner = new Point(ObstacleTree.GraphBox.Left - SentinelOffset, ObstacleTree.GraphBox.Bottom - SentinelOffset);
var upperCorner = new Point(ObstacleTree.GraphBox.Left - SentinelOffset, ObstacleTree.GraphBox.Top + SentinelOffset);
var sentinel = Obstacle.CreateSentinel(lowerCorner, upperCorner, ScanDirection, scanlineSentinelOrdinal++);
scanLine.Insert(sentinel.ActiveHighSide, ObstacleTree.GraphBox.LeftBottom);
// High sentinel...
lowerCorner = new Point(ObstacleTree.GraphBox.Right + SentinelOffset, ObstacleTree.GraphBox.Bottom - SentinelOffset);
upperCorner = new Point(ObstacleTree.GraphBox.Right + SentinelOffset, ObstacleTree.GraphBox.Top + SentinelOffset);
sentinel = Obstacle.CreateSentinel(lowerCorner, upperCorner, ScanDirection, scanlineSentinelOrdinal++);
scanLine.Insert(sentinel.ActiveLowSide, ObstacleTree.GraphBox.LeftBottom);
// Process the Hscan events.`
DevTraceInfoVgGen(1, "Processing Horizontal Scan events");
ProcessEvents();
// Now do the horizontal sweep (vertical scan). Note: Because we use Cartesian coordinates
// rather than Page coordinates, the High and Low sides reverse the direction they traverse
// when doing vertical scan; this information is passed as a ctor param. This allows for
// consistent comparisons.
InitializeEventQueue(ScanDirection.VerticalInstance);
// Lower sentinel...
lowerCorner = new Point(ObstacleTree.GraphBox.Left - SentinelOffset, ObstacleTree.GraphBox.Bottom - SentinelOffset);
upperCorner = new Point(ObstacleTree.GraphBox.Right + SentinelOffset, ObstacleTree.GraphBox.Bottom - SentinelOffset);
sentinel = Obstacle.CreateSentinel(lowerCorner, upperCorner, ScanDirection, scanlineSentinelOrdinal++);
scanLine.Insert(sentinel.ActiveHighSide, ObstacleTree.GraphBox.LeftBottom);
// Upper sentinel
lowerCorner = new Point(ObstacleTree.GraphBox.Left - SentinelOffset, ObstacleTree.GraphBox.Top + SentinelOffset);
upperCorner = new Point(ObstacleTree.GraphBox.Right + SentinelOffset, ObstacleTree.GraphBox.Top + SentinelOffset);
sentinel = Obstacle.CreateSentinel(lowerCorner, upperCorner, ScanDirection, scanlineSentinelOrdinal);
scanLine.Insert(sentinel.ActiveLowSide, ObstacleTree.GraphBox.LeftBottom);
// Process the Vscan events.
DevTraceInfoVgGen(1, "Processing Vertical Scan events");
ProcessEvents();
}
[Conditional("DEBUG")]
// ReSharper disable InconsistentNaming
protected static void Debug_AssertGraphIsRectilinear(VisibilityGraph graph, ObstacleTree obstacleTree)
// ReSharper restore InconsistentNaming
{
#if DEBUG
if (graph.Edges.Any(edge => !PointComparer.IsPureDirection(PointComparer.GetDirections(edge.SourcePoint, edge.TargetPoint))))
{
StaticGraphUtility.Assert(false, "Generated VisibilityGraph contains non-rectilinear lines", obstacleTree, graph);
return;
}
#endif
}
internal static Point ScanLineIntersectSide(Point site, BasicObstacleSide side, ScanDirection scanDir)
{
// Note: we don't assert that site and side are not PointComparer.Equal, because ScanLine calls
// this on sides that share vertices.
Debug.Assert(!scanDir.IsFlat(side), "flat sides should not be in the scanline or encountered on lookahead scan");
// We know that we will have an intersection if the side is adjacent in the scanline, so
// we can optimize the calculation to project along the slope of the BasicObstacleSide.
// Also, due to rounding, we need to make sure that when intersecting the side, we're not
// falling short due to rounding error; that can be a problem if we're right at a vertex
// of that obstacle, because then there is no intersection with the perpendicular line
// from that vertex. So make sure we are at least to the nearest coordinate of that side.
// Note: Calculate slope here using 'dir' rather than side.SlopeInverse because Reflection
// lookaheads calculate the perpendicular intersection and side.Slope(Inverse) is always
// relative to the scanline parallel.
Point dir = side.Direction;
#if SHARPKIT //https://code.google.com/p/sharpkit/issues/detail?id=369
Point intersect = side.Start.Clone();
#else
Point intersect = side.Start;
#endif
if (scanDir.IsHorizontal) {
intersect.X += (dir.X / dir.Y) * (site.Y - side.Start.Y);
intersect.X = SpliceUtility.MungeIntersect(site.X, intersect.X, side.Start.X, side.End.X);
intersect.Y = site.Y;
}
else {
intersect.X = site.X;
intersect.Y += (dir.Y / dir.X) * (site.X - side.Start.X);
intersect.Y = SpliceUtility.MungeIntersect(site.Y, intersect.Y, side.Start.Y, side.End.Y);
}
return intersect;
}
PolylinePoint GetOpenVertex(Polyline poly) {
PolylinePoint lowest = poly.StartPoint;
PolylinePoint next = TraversePolylineForEvents(lowest);
// We want the bottom vertex to be the lowest in scanline-parallel coordinate if there is a
// flat bottom side, and we've guaranteed that the lines are oriented clockwise. This means
// that we want a <= comparison of the current node vs. the candidate, to store the last
// lowest vertex of the clockwise rotation. Stop when we've turned upward from descending/flat.
int iPrevCmp = PointCompare(next.Point, lowest.Point);
for (; ; next = TraversePolylineForEvents(next)) {
int iCurCmp = PointCompare(next.Point, lowest.Point);
if (iCurCmp <= 0) {
lowest = next;
} else if ((iCurCmp > 0) && (iPrevCmp <= 0)) {
break;
}
iPrevCmp = iCurCmp;
}
return lowest;
}
PolylinePoint TraversePolylineForEvents(PolylinePoint polyPoint) {
// When loading scanline events, we'll go clockwise for horizontal scan, where the
// scanline-parallel coordinate increases to the right, or counterclockwise for vertical
// scan, where the scanline-parallel coordinate increases to the left.
if (ScanDirection.IsHorizontal) {
return polyPoint.NextOnPolyline;
}
return polyPoint.PrevOnPolyline;
}
internal virtual void InitializeEventQueue(ScanDirection scanDir) {
ScanDirection = scanDir;
eventQueue.Reset(ScanDirection);
EnqueueBottomVertexEvents();
scanLine = new RectilinearScanLine(ScanDirection, ObstacleTree.GraphBox.LeftBottom);
lookaheadScan = new LookaheadScan(ScanDirection);
}
void EnqueueBottomVertexEvents() {
foreach (var obstacle in ObstacleTree.GetAllPrimaryObstacles()) {
PolylinePoint bottomVertex = GetOpenVertex(obstacle.VisibilityPolyline);
eventQueue.Enqueue(new OpenVertexEvent(obstacle, bottomVertex));
}
} // end EnqueueBottomVertexEvents
internal bool IsFlat(BasicObstacleSide side) {
return ScanDirection.IsFlat(side);
}
internal bool IsPerpendicular(BasicObstacleSide side) {
// If it's perpendicular we won't generate reflections.
return ScanDirection.IsPerpendicular(side);
}
// Params are event site (vertex point) and the obstacle side adjacent to that site.
protected Point ScanLineIntersectSide(Point site, BasicObstacleSide side) {
return ScanLineIntersectSide(site, side, ScanDirection);
}
protected bool SideReflectsUpward(BasicObstacleSide side) {
// Returns false if vertical.
if (side is LowObstacleSide) {
// Low side slopes upward if slope is positive (to the high direction).
return ScanDirection.Coord(side.End) > ScanDirection.Coord(side.Start);
}
// High side slopes upward if slope is negative (to the low direction).
return ScanDirection.Coord(side.End) < ScanDirection.Coord(side.Start);
}
bool SideReflectsDownward(BasicObstacleSide side) {
// Returns false if vertical.
if (side is LowObstacleSide) {
// Low side slopes downward if slope is negative (to the low direction).
return ScanDirection.Coord(side.End) < ScanDirection.Coord(side.Start);
}
// High side slopes downward if slope is positive (to the high direction).
return ScanDirection.Coord(side.End) > ScanDirection.Coord(side.Start);
}
// Calculate reflections from the lines, depending on line side (Low vs. High) and slope.
// Because the low neighbor intersection is on a high side of its obstacle
// and vice-versa, then the "side" of a lowNbor is a highSide, and vice versa.
protected void StoreLookaheadSite(Obstacle initialObstacle, BasicObstacleSide reflectingSide, Point reflectionSite, bool wantExtreme) {
if (!this.wantReflections) {
return;
}
// If the line is perpendicular, we won't generate reflections (they'd be redundant).
if (!IsPerpendicular(reflectingSide)) {
// If this is hitting an extreme vertex in the forward direction, we will (or already did) create a normal
// ScanSegment in the perpendicular direction.
if (!wantExtreme && !StaticGraphUtility.PointIsInRectangleInterior(reflectionSite, reflectingSide.Obstacle.VisibilityBoundingBox)) {
return;
}
// We can only do upward reflections, which fortunately is all we need.
if (SideReflectsUpward(reflectingSide)) {
// We defer actually creating the perpendicular line until we've processed
// the reflection event we're about to enqueue, so we may legitimately encounter
// two (or more) reflections at the same point along the parallel-to-scanline
// coordinate, if we have a side that is nearly but not quite perpendicular to
// the scanline. For example:
// \
// \----------------------------- line2
// \---------------------------- line1
// Assume the vertical side is very close to perpendicular; then as we process
// the horizontal lines in the upward direction, we may generate two lookahead
// reflections at coordinates that are sufficiently far apart in the vertical
// coordinate (in this example, Y) that the lines are not subsumed, but are
// sufficiently close in the horizontal coordinate (in this example, X) that
// the perpendicular lines would be subsumed. In that case, we look here to see
// if there is an active lookahead scan for the reflecting site's parallel coordinate;
// if so, then because we know that the side reflects upward, we also know that
// the perpendicular line from the lowest segment's reflection site will intersect
// the higher segments here, providing the VisibilityVertices we need, so we can
// safely ignore the duplicate lookahead.
// Don't worry about enqueueing a reflection at the extreme scanline-parallel
// vertex because we'll MergeSegments to handle that.
if (null == lookaheadScan.Find(reflectionSite)) {
lookaheadScan.Add(new BasicReflectionEvent(initialObstacle, reflectingSide.Obstacle, reflectionSite));
DevTraceInfoVgGen(1, "Storing reflection lookahead site {0}", reflectionSite);
}
else {
DevTraceInfoVgGen(1, "Reflection lookahead site {0} already exists", reflectionSite);
}
}
}
} // end StoreLookaheadSite()
// Load any lookahead scan ray intersections with a side we've just added.
protected void LoadReflectionEvents(BasicObstacleSide sideToQueue) {
LoadReflectionEvents(sideToQueue, sideToQueue);
}
// sideWithRange is either the same as sideToQueue, if that side is being loaded by an
// OpenVertexEvent, or is a different side that is just closing.
protected void LoadReflectionEvents(BasicObstacleSide sideToQueue, BasicObstacleSide sideWithRange) {
// If this line reflects upward then it cannot receive rays from below (they would pass
// through its obstacle), and of course a perpendicular lookahead line will never
// intersect a perpendicular side.
if ((null == sideToQueue) || SideReflectsUpward(sideToQueue) || IsPerpendicular(sideToQueue)) {
return;
}
// If there is no overlap in the rectangles along the current axis, there is nothing
// to do. This reduces (but doesn't prevent) duplicate events being loaded.
var bbox1 = new Rectangle(sideToQueue.Start, sideToQueue.End);
var bbox2 = new Rectangle(sideWithRange.Start, sideWithRange.End);
if ((ScanDirection.IsHorizontal) ? !bbox1.IntersectsOnX(bbox2) : !bbox1.IntersectsOnY(bbox2)) {
return;
}
// Make sure we order the endpoints from low to high along the scanline parallel, and get only
// the intersection. RectilinearFileTests.Nudger_Overlap* exercise reflection lookahead subranges.
Rectangle bboxIntersect = Rectangle.Intersect(bbox1, bbox2);
Point low = bboxIntersect.LeftBottom;
Point high = bboxIntersect.RightTop;
// This is inclusive of the endpoints of sideWithRange, to be sure that we remove the item
// from LookaheadScan; if it's on an extreme vertex in the perpendicular sweep then it will
// stop the chain; see TestRectilinear.Reflection_Staircase_Stops_At_BoundingBox_Side*.
RBNode<BasicReflectionEvent> lookaheadSiteNode = lookaheadScan.FindFirstInRange(low, high);
while (null != lookaheadSiteNode) {
// Calculate the lookahead intersection with this side in the perpendicular direction to
// the scanline. Note: due to rounding error, this may be different from the calculation
// in the parallel direction when the scanline gets up to the ScanDirection.PerpCoord(intersect);
// this will be adjusted in ScanSegmentTree.MergeSegments.
Point intersect = ScanLineIntersectSide(lookaheadSiteNode.Item.Site, sideToQueue
, ScanDirection.PerpendicularInstance);
DevTraceInfoVgGen(1, "Loading reflection from lookahead site {0} to intersect at {1}"
, lookaheadSiteNode.Item.Site, intersect);
DevTraceInfoVgGen(2, " side {0})", sideToQueue); // same indent as AddSegment
// In some cases where the ActiveLowSide and ActiveHighSide of an obstacle lean in the same
// direction such that LowSide is above HighSide, e.g. on the horizontal pass when they both
// lean to the right, the delayed-lookahead in CloseVertex (obstacle close) event may find the
// high side spanning the scanline-parallel coordinate range where its low side has enqueued
// lookahead events. In that case the intersection will be less than the enqueueing site so
// ignore it. See RectilinearTests.ReflectionsSitedByLowSideAreNotLoadedByHighSide.)
// Similarly, if this is at the same perpendicular coordinate as the current scanline
// position, ignore it; otherwise we could back up in the scanline's parallel coordinate.
// Since we retrieved events for the endpoint of any previous side, this won't be
// encountered on a bend vertex event; therefore we're on a near-flat bottom side
// so we're parallel to the extreme-vertex line and it's fine to just absorb the photon.
// This also handles the case of reflections into intersecting sides - at some point
// they converge such that the intersection is not ahead of the lookahead site.
if (ScanDirection.ComparePerpCoord(intersect, lookaheadSiteNode.Item.Site) > 0) {
// Add an event to continue the chain, "shifting" the site's reflecting
// obstacle back to the initialObstacle position. We must load this here
// and process it in ConfirmLookaheadEvent so it will be removed from
// the lookahead list; we can't remove it here if it doesn't satisfy the
// staircase requirements, because this may be called from loading a "higher"
// side (during the sweep) which could steal events from the lower side.
AddReflectionEvent(lookaheadSiteNode.Item, sideToQueue, intersect);
}
else {
if (lookaheadSiteNode.Item.ReflectingObstacle != sideToQueue.Obstacle) {
DevTraceInfoVgGen(1, " (discarding reflection at intersect {0} as it is not ahead of the previous site)", intersect);
// We need to remove the site. We're in the middle of Node enumeration so just
// mark the site and on function exit we'll remove any so marked.
lookaheadScan.MarkStaleSite(lookaheadSiteNode.Item);
}
else {
DevTraceInfoVgGen(1, " (skipping reflection at intersect {0} as it is the same obstacle)", intersect);
}
}
// Get the next item, leaving the current one in the lookahead scan until
// we actually process the event; this lets us know whether an intervening
// obstacle may be opened and intercepted the reflection. ConfirmLookaheadEvents
// will actually do the removal when the lowest side containing the lookahead
// site is loaded. See RectilinearTests.ReflectionsRemoveInterceptedSite.
lookaheadSiteNode = lookaheadScan.FindNextInRange(lookaheadSiteNode, high);
} // endwhile previousSiteNode
lookaheadScan.RemoveStaleSites();
}
// Determine whether the event is valid and do some common processing.
bool AddPerpendicularReflectionSegment(BasicReflectionEvent currentEvent, BasicObstacleSide eventSide, BasicObstacleSide nborSide) {
// If eventSide is null it means we had the wrong side type as a scanline neighbor.
// If another obstacle opened up, then that obstacle (or another intervening one) should have
// drained this reflection event.
Debug.Assert(null != eventSide, "eventSide should not be null");
// Between the time currentEvent was queued and the time we're now processing it, another
// obstacle may have opened between the previousSite and the eventSite, in which case it
// removed currentEvent from the queue already. So currentEvent may be stale. The new
// obstacle may have stored *another* lookahead site with the same scanline-parallel
// coordinate (but higher up perpendicularly). So remove the exact site of currentEvent;
// otherwise the currentEvent could be a stale event with the lower scanline-parallel
// coordinate, and would remove the site from the lookahead list before the "live" event
// looks for it. See RectilinearTests.ReflectionsRemoveInterceptedSite.
if (lookaheadScan.RemoveExact(currentEvent.PreviousSite)) {
Debug.Assert(currentEvent.InitialObstacle == currentEvent.PreviousSite.ReflectingObstacle
, "Inconsistency: currentEvent.InitialObstacle != currentEvent.PreviousSite.ReflectingObstacle");
// ReSharper disable HeuristicUnreachableCode
// ReSharper disable ConditionIsAlwaysTrueOrFalse
if (null == eventSide) {
// We've removed the event so there's nothing else to do.
return false;
}
// ReSharper restore ConditionIsAlwaysTrueOrFalse
// ReSharper restore HeuristicUnreachableCode
// If the two sides intersect ahead of the scanline, we don't want the reflection.
// If the reflecting side is flat, no reflection is done - that's handled by OpenVertexEvent.
Debug.Assert(!IsFlat(eventSide), "Flat sides should not be encountered in reflections");
if (currentEvent.PreviousSite.IsStaircaseStep(currentEvent.ReflectingObstacle)) {
// We need to draw the perpendicular lines here because we may be on the second
// sweep so there won't be a subsequent sweep to draw them. And if we're on the
// second sweep, we may have already loaded this segment as part of a continuation
// of an overlapped segment. Either way, we only want this if we are not on an extreme
// edge of the target obstacle (reflectingObstacle for the perpendicular segment,
// nborSide.Obstacle for the parallel segment). Extreme vertices will generate segments.
// See TestRectilinear.Reflection_Staircase_Stops_At_BoundingBox_Side*.
if (!StaticGraphUtility.PointIsInRectangleInterior(currentEvent.Site, currentEvent.ReflectingObstacle.VisibilityBoundingBox))
{
return false;
}
DevTraceInfoVgGen(1, "Perpendicular Reflection - Adding Segment [{0} -> {1}]", currentEvent.PreviousSite.Site, currentEvent.Site);
DevTraceInfoVgGen(2, " -> side {0}", eventSide); // same indent as AddSegment; eventSide is highNbor
if (!InsertPerpendicularReflectionSegment(currentEvent.PreviousSite.Site, currentEvent.Site)) {
return false;
}
// If the neighbor continues the staircase and the parallel segment would hit a non-extreme point
// on the neighbor, return true and the Low/HighReflectionEvent handler will add the parallel segment.
if ((null != nborSide) && currentEvent.IsStaircaseStep(nborSide.Obstacle)) {
return this.ScanLineCrossesObstacle(currentEvent.Site, nborSide.Obstacle);
}
DevTraceInfoVgGen(1, "Reflection Lookahead site {0} is not an outgoing staircase step; discontinuing", currentEvent.PreviousSite);
}
else {
DevTraceInfoVgGen(1, "Reflection Lookahead site {0} is not an incoming staircase step; discontinuing", currentEvent.PreviousSite);
}
}
else {
DevTraceInfoVgGen(1, "Reflection Lookahead site {0} is no longer in the lookahead table; skipping", currentEvent.PreviousSite);
}
return false;
}
protected abstract bool InsertPerpendicularReflectionSegment(Point start, Point end);
private bool AddParallelReflectionSegment(Obstacle eventObstacle, BasicObstacleSide lowNborSide
, BasicObstacleSide highNborSide, BasicReflectionEvent action) {
// If this is reflecting to a low neighbor, then that intersect is 'start' in the low-to-high
// sequence, and the event site is the end; otherwise we start at the event site and end at
// the high neighbor.
Point intersect = ScanLineIntersectSide(action.Site, lowNborSide ?? highNborSide);
Point start = (null != lowNborSide) ? intersect : action.Site;
Point end = (null != lowNborSide) ? action.Site : intersect;
// Now get the opposite neighbors so AddSegment can continue the reflection chain.
if (null == lowNborSide) {
lowNborSide = scanLine.NextLow(highNborSide).Item;
}
else {
highNborSide = scanLine.NextHigh(lowNborSide).Item;
}
return InsertParallelReflectionSegment(start, end, eventObstacle, lowNborSide, highNborSide, action);
}
protected abstract bool InsertParallelReflectionSegment(Point start, Point end, Obstacle eventObstacle,
BasicObstacleSide lowNborSide, BasicObstacleSide highNborSide, BasicReflectionEvent action);
void AddReflectionEvent(BasicReflectionEvent previousSite, BasicObstacleSide side, Point site) {
Debug.Assert(null != scanLine.Find(side), "AddReflectionEvent could not find 'side' in the scanline");
// Add an event that will be drained when a side spanning the scanline-parallel is loaded
// as the sweep moves "up".
var lowSide = side as LowObstacleSide;
if (lowSide != null) {
eventQueue.Enqueue(new LowReflectionEvent(previousSite, lowSide, site));
} else {
eventQueue.Enqueue(new HighReflectionEvent(previousSite, (HighObstacleSide)side, site));
}
}
RBNode<BasicObstacleSide> AddSideToScanLine(BasicObstacleSide side, Point scanPos) {
RBNode<BasicObstacleSide> node = scanLine.Insert(side, scanPos);
// Now get any pending LookaheadScan intersections along this side.
LoadReflectionEvents(side);
return node;
}
void RemoveSideFromScanLine(RBNode<BasicObstacleSide> sideNode, Point scanPos) {
scanLine.Remove(sideNode.Item, scanPos);
}
int PointCompare(Point lhs, Point rhs) {
return ScanDirection.Compare(lhs, rhs);
}
internal virtual void Clear() {
ObstacleTree.Clear();
eventQueue = new EventQueue();
HorizontalScanSegments = new ScanSegmentTree(ScanDirection.HorizontalInstance);
VerticalScanSegments = new ScanSegmentTree(ScanDirection.VerticalInstance);
VisibilityGraph = null;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
private void ProcessEvents() {
// Note: Sentinel vertices are not in EventQueue so eventcount will go to 0.
while (eventQueue.Count > 0) {
SweepEvent evt = eventQueue.Dequeue();
if (evt is OpenVertexEvent) {
ProcessEvent(evt as OpenVertexEvent);
} else if (evt is LowBendVertexEvent) {
ProcessEvent(evt as LowBendVertexEvent);
} else if (evt is HighBendVertexEvent) {
ProcessEvent(evt as HighBendVertexEvent);
} else if (evt is CloseVertexEvent) {
ProcessEvent(evt as CloseVertexEvent);
} else if (evt is LowReflectionEvent) {
ProcessEvent(evt as LowReflectionEvent);
} else if (evt is HighReflectionEvent) {
ProcessEvent(evt as HighReflectionEvent);
} else {
ProcessCustomEvent(evt);
}
#if DEVTRACE
if (evt is BasicVertexEvent) {
scanLine.DevTrace_VerifyConsistency("after {0}", evt);
}
#endif // DEVTRACE
LowNeighborSides.Clear();
HighNeighborSides.Clear();
} // endwhile there are events
// Ensure we have no leftovers in the scanline - we should have the two sentinels and nothing else.
Debug.Assert(2 == scanLine.Count, "There are leftovers in the scanline");
}
protected virtual void ProcessCustomEvent(SweepEvent evt)
{
// These are events specific to the derived class; by default there are none.
Debug.Assert(false, "Unknown event type");
}
protected bool ScanLineCrossesObstacle(Point eventSite, Obstacle obstacle) {
// An inner or outer neighbor's side is only an overlap start/stop candidate if its obstacle
// brackets the open/close event's Perpendicular Scan coord.
return (ScanDirection.ComparePerpCoord(eventSite, obstacle.VisibilityBoundingBox.LeftBottom) > 0)
&& (ScanDirection.ComparePerpCoord(eventSite, obstacle.VisibilityBoundingBox.RightTop) < 0);
}
void FindInitialNeighborSides(RBNode<BasicObstacleSide> sideNode, out RBNode<BasicObstacleSide> lowNborSideNode,
out RBNode<BasicObstacleSide> highNborSideNode)
{
lowNborSideNode = this.scanLine.NextLow(sideNode);
highNborSideNode = this.scanLine.NextHigh(sideNode);
}
// As described in the doc, we stop at the first neighbor of the appropriate side type that we touch
// the border of, even if that's just skimming along the extreme vertex of it, because those will
// continue the chain of open/close+addSegment, and we don't want to follow the full length of the
// segment each time if there are a lot of collinear obstacle open/close events.
protected void FindNeighbors(BasicVertexEvent vertexEvent, RBNode<BasicObstacleSide> lowSideNode, RBNode<BasicObstacleSide> highSideNode) {
LowNeighborSides.Clear();
HighNeighborSides.Clear();
// Find the first HighObstacleSide in the low (scanline-decreasing) direction (this may be the low
// sentinel) and the lowest LowObstacleSide toward that that we cross *through*, if any. Then do
// the same thing in the high direction. If we are not overlapped, then we'll jump out immediately
// from SkipToNeighbor, so there won't be a lot of redundant effort in that case.
FindNeighbors(vertexEvent, lowSideNode, LowNeighborSides);
FindNeighbors(vertexEvent, highSideNode, HighNeighborSides);
}
protected void FindNeighbors(BasicVertexEvent vertexEvent, RBNode<BasicObstacleSide> sideNode, NeighborSides neighborSides) {
// vertexEvent.Site is on one of vertexEvent.Obstacle.Active(Low|High)Side, so we must get the
// appropriate vertex on whichever one of those Active*Sides is sideNode.
var sideReferencePoint = (vertexEvent is OpenVertexEvent) ? sideNode.Item.Start : sideNode.Item.End;
RBNode<BasicObstacleSide> initialLowNbor, initialHighNbor;
this.FindInitialNeighborSides(sideNode, out initialLowNbor, out initialHighNbor);
this.SkipToNeighbor(this.ScanDirection.OppositeDirection, sideNode.Item, sideReferencePoint, initialLowNbor, neighborSides);
this.SkipToNeighbor(this.ScanDirection.Direction, sideNode.Item, sideReferencePoint, initialHighNbor, neighborSides);
}
void SkipToNeighbor(Directions nborSearchDir, BasicObstacleSide side, Point sideReferencePoint,
RBNode<BasicObstacleSide> nborNode, NeighborSides neighborSides) {
// Find the first neighbor side (LowObstacleSide if going high, HighObstacleSide if going low) and
// the side of opposite type (which would potentially end overlap), that that we cross *through*, if any.
RBNode<BasicObstacleSide> overlapSideNode = null;
BasicObstacleSide interveningGroupSide = null;
for (; ; nborNode = scanLine.Next(nborSearchDir, nborNode)) {
// Ignore the opposite side of the current obstacle.
if (nborNode.Item.Obstacle == side.Obstacle) {
continue;
}
if (nborNode.Item.Obstacle.IsGroup) {
if (ProcessGroupSideEncounteredOnTraversalToNeighbor(nborNode, sideReferencePoint, nborSearchDir)) {
// Keep the first one (outermost) encountered.
if (null == interveningGroupSide) {
interveningGroupSide = nborNode.Item;
}
}
continue;
}
// Check for overlap-ending obstacle.
if ((nborNode.Item is HighObstacleSide) == StaticGraphUtility.IsAscending(nborSearchDir)) {
if (ScanLineCrossesObstacle(sideReferencePoint, nborNode.Item.Obstacle)) {
overlapSideNode = nborNode;
interveningGroupSide = null;
}
continue;
}
// If we're here, we found the neighbor we were looking for.
break;
}
neighborSides.SetSides(nborSearchDir, nborNode, overlapSideNode, interveningGroupSide);
}
private bool ProcessGroupSideEncounteredOnTraversalToNeighbor(RBNode<BasicObstacleSide> nborNode, Point sideReferencePoint,
Directions nborSearchDir) {
if (!this.ScanLineCrossesObstacle(sideReferencePoint, nborNode.Item.Obstacle)) {
return false;
}
// We don't stop overlap or neighbor-traversal for groups, because we must go through the boundary;
// 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.
Directions dirToInsideOfGroup = ((nborNode.Item is LowObstacleSide) == StaticGraphUtility.IsAscending(nborSearchDir))
? nborSearchDir : CompassVector.OppositeDir(nborSearchDir);
var intersect = this.ScanLineIntersectSide(sideReferencePoint, nborNode.Item);
this.CurrentGroupBoundaryCrossingMap.AddIntersection(intersect, nborNode.Item.Obstacle, dirToInsideOfGroup);
return true;
}
void FindNeighborsAndProcessVertexEvent(RBNode<BasicObstacleSide> lowSideNode
, RBNode<BasicObstacleSide> highSideNode
, BasicVertexEvent vertexEvent) {
CurrentGroupBoundaryCrossingMap.Clear();
FindNeighbors(vertexEvent, lowSideNode, highSideNode);
this.ProcessVertexEvent(lowSideNode, highSideNode, vertexEvent);
// Clear this again because we don't want Reflections to access stale values.
CurrentGroupBoundaryCrossingMap.Clear();
}
protected abstract void ProcessVertexEvent(RBNode<BasicObstacleSide> lowSideNode,
RBNode<BasicObstacleSide> highSideNode, BasicVertexEvent vertexEvent);
[Conditional("DEVTRACE")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
// ReSharper disable InconsistentNaming
protected void DevTrace_DumpScanSegmentsDuringAdd(int verboseLevel) {
// ReSharper restore InconsistentNaming
#if DEVTRACE
DevTraceScanSegmentDump(verboseLevel);
#endif // DEVTRACE
}
void ProcessEvent(OpenVertexEvent openVertEvent) {
// First insert the two new lines into the scanline. Note: Although the lines are clockwise oriented,
// LowObstacleSide and HighObstacleSide take a parameter to know when to go counterclockwise.
var obstacle = openVertEvent.Obstacle;
obstacle.CreateInitialSides(openVertEvent.Vertex, ScanDirection);
Debug.Assert(!IsFlat(obstacle.ActiveLowSide), "OpenVertexEvent ActiveLowSide should not be flat");
Debug.Assert(!IsFlat(obstacle.ActiveHighSide), "RemoveCollinearSides should have been called");
DevTraceIfFlatSide(true /*isObstacleOpen*/, obstacle.ActiveLowSide.Start, obstacle.ActiveHighSide.Start);
// Adding can rotate the RBTree which modifies RBNodes so get the lowSideNode after adding highSideNode.
// AddSideToScanLine loads any reflection events for the side.
AddSideToScanLine(obstacle.ActiveLowSide, openVertEvent.Site);
RBNode<BasicObstacleSide> highSideNode = AddSideToScanLine(obstacle.ActiveHighSide, openVertEvent.Site);
RBNode<BasicObstacleSide> lowSideNode = scanLine.Find(obstacle.ActiveLowSide);
// Get the neighbors. In the simple, non-overlapped case, we'll generate a segment between them which
// includes the vertex point (and any flat border of the current obstacle). These neighbors will
// never be null; one or both may be the fake sentinel borders at the graphBox limits.
this.FindNeighborsAndProcessVertexEvent(lowSideNode, highSideNode, openVertEvent);
// Look for Reflections. If the ScanSegments we just added generated any
// ReflectionEvents, then the Active*Side to that side may cover them, or if we're overlapped,
// the Active*Side of the overlapping obstacle may if there is a non-overlapped segment extension.
// Check the neighbors in both directions.
var lowReflector = LowNeighborSides.GroupSideInterveningBeforeLowNeighbor ?? LowNeighborSides.LowNeighborSide;
if (SideReflectsUpward(lowReflector)) {
LoadReflectionEvents(obstacle.ActiveLowSide);
}
var highReflector = HighNeighborSides.GroupSideInterveningBeforeHighNeighbor ?? HighNeighborSides.HighNeighborSide;
if (SideReflectsUpward(highReflector)) {
LoadReflectionEvents(obstacle.ActiveHighSide);
}
// If this is a flat side it must absorb any outstanding reflection sites.
if (obstacle.ActiveHighSide.Start != obstacle.ActiveLowSide.Start) {
// Create a temp HighObstacleSide so the "next vertex" moves in the correct direction.
var tempSide = new HighObstacleSide(obstacle, openVertEvent.Vertex, ScanDirection);
lookaheadScan.RemoveSitesForFlatBottom(tempSide.Start, tempSide.End);
}
// Add events for the low and high sides.
EnqueueLowBendVertexEvent(obstacle.ActiveLowSide);
EnqueueHighBendOrCloseVertexEvent(obstacle.ActiveHighSide);
} // end ProcessEvent(OpenVertexEvent)
void ProcessEvent(LowBendVertexEvent lowVertEvent) {
// Note: we only draw lines only from "interesting" vertices, which would be those
// that open or close an obstacle, as well as staircase Reflections (see doc). This means
// Low/HighVertexEvents routines will just track the change in ActiveLowSide/ActiveHighSide.
// This is a vertex on the low side of the obstacle. Update the ActiveLowSide in the obstacle
// and scanline (this also checks for Reflection events).
var obstacle = lowVertEvent.Obstacle;
var lowSide = new LowObstacleSide(obstacle, lowVertEvent.Vertex, ScanDirection);
// If the new lowSide is flat we don't remove it due to potential overlaps; that lets any collinear
// OpenVertexEvents know they are overlapped (touching == overlap). When we get to CloseVertexEvent,
// keeping the current ActiveLowSide in the scanline tells us how when the interior sides stop
// so we know when we've found a neighbor. Similarly, if we're turning down toward the
// scanline, we let CloseVertexEvent remove the side (in case there are coincident vertices).
// That leaves the case of still ascending, where we replace the side in the scanline.
if (ScanDirection.ComparePerpCoord(lowSide.End, lowSide.Start) > 0) {
this.RemoveSideFromScanLine(this.scanLine.Find(obstacle.ActiveLowSide), lowVertEvent.Site);
AddSideToScanLine(lowSide, lowVertEvent.Site);
obstacle.ActiveLowSide = lowSide;
EnqueueLowBendVertexEvent(lowSide);
}
} // end ProcessEvent(LowBendVertexEvent)
void EnqueueLowBendVertexEvent(BasicObstacleSide lowSide) {
// We've already ensured the extension is valid so just queue the next event.
eventQueue.Enqueue(new LowBendVertexEvent(lowSide.Obstacle, lowSide.EndVertex));
}
void ProcessEvent(HighBendVertexEvent highVertEvent) {
// See comments in LowBendVertexEvent; this is mostly the same thing to the other side.
var obstacle = highVertEvent.Obstacle;
var highSide = new HighObstacleSide(obstacle, highVertEvent.Vertex, ScanDirection);
this.RemoveSideFromScanLine(this.scanLine.Find(obstacle.ActiveHighSide), highVertEvent.Site);
RBNode<BasicObstacleSide> highSideNode = AddSideToScanLine(highSide, highVertEvent.Site);
obstacle.ActiveHighSide = highSide;
EnqueueHighBendOrCloseVertexEvent(obstacle.ActiveHighSide);
// If this is an extreme high-side lateral vertex on the horizontal pass turning to an upward-reflecting
// side - i.e. if it is a vertex on the right-most border of the bounding box, and the new HighObstacleSide
// has negative slope - then we may have a situation where a neighbor LowObstacleSide reflects downward and
// spans this entire HighObstacleSide and a little more:
// #
// #
// . #
// \ #
// . #
// #
// The ".\." side is completely spanned by the '#' side and because of the tilt directions, lookahead
// will not happen, therefore reflections will not happen and there will be no scansegments between the
// two sides. This could lead to spurious overlaps. So in this case we drop in an extra lookahead.
// (This is not an issue for other tilt directions - there is always a reflection chain generated).
// Test is RectilinearFileTests.Overlap_ExtremeSide_Lookahead.
if (this.wantReflections && this.ScanDirection.IsHorizontal
&& (highSide.Start.X == obstacle.VisibilityBoundingBox.Right)
&& SideReflectsUpward(highSide)) {
var nborSideNode = this.scanLine.NextHigh(highSideNode);
if ((nborSideNode.Item is LowObstacleSide) && this.SideReflectsDownward(nborSideNode.Item)) {
if (!obstacle.IsOverlapped || !this.ObstacleTree.PointIsInsideAnObstacle(highSide.Start, this.ScanDirection)) {
this.StoreLookaheadSite(nborSideNode.Item.Obstacle, highSide, highSide.Start, wantExtreme: true);
LoadReflectionEvents(nborSideNode.Item);
}
}
}
}
void EnqueueHighBendOrCloseVertexEvent(BasicObstacleSide highSide) {
// If the next side segment after highSide is ascending from the scanline we want to queue another
// HighBendVertexEvent; otherwise it is flat or turns down toward the scanline so queue a CloseVertexEvent.
Obstacle obstacle = highSide.Obstacle;
PolylinePoint nextHighSideEnd = ScanDirection.IsHorizontal // Traverse clockwise or counterclockwise
? highSide.EndVertex.PrevOnPolyline
: highSide.EndVertex.NextOnPolyline;
if (ScanDirection.ComparePerpCoord(nextHighSideEnd.Point, highSide.End) > 0) {
eventQueue.Enqueue(new HighBendVertexEvent(obstacle, highSide.EndVertex));
}
else {
eventQueue.Enqueue(new CloseVertexEvent(obstacle, highSide.EndVertex));
}
} // end ProcessEvent(HighBendVertexEvent)
void CreateCloseEventSegmentsAndFindNeighbors(CloseVertexEvent closeVertEvent) {
var obstacle = closeVertEvent.Obstacle;
DevTraceIfFlatSide(false /*isObstacleOpen*/, obstacle.ActiveLowSide.End, obstacle.ActiveHighSide.End);
RBNode<BasicObstacleSide> lowSideNode = scanLine.Find(obstacle.ActiveLowSide);
RBNode<BasicObstacleSide> highSideNode = scanLine.Find(obstacle.ActiveHighSide);
// Two sides coming together at a top point will be reverse-ordered in the scanline,
// because their projections ahead of the intersection are slope-based. This must
// be the case in order to maintain scanline consistency. Therefore we need to
// check the comparison and reverse the local variables if necessary. Fortunately
// we only concern ourselves with the actual Low-vs-High side type for neighbors,
// not the sides of the obstacle.
if (1 == scanLine.Compare(obstacle.ActiveLowSide, obstacle.ActiveHighSide)) {
var temp = lowSideNode;
lowSideNode = highSideNode;
highSideNode = temp;
}
// As with OpenVertexEvent, the idea here is to find the neighbors and draw the line between them
// that includes the event vertex (and any flat top obstacle side), with consideration for overlaps.
this.FindNeighborsAndProcessVertexEvent(lowSideNode, highSideNode, closeVertEvent);
// Inner overlaps: any overlapped sides coming out of the obstacle must have reflection events
// drained for any that were generated from sides of the closing obstacle if they extend
// outside the closing obstacle.
if (this.wantReflections && obstacle.IsOverlapped) {
for (RBNode<BasicObstacleSide> nextNode = scanLine.NextHigh(lowSideNode);
nextNode.Item != highSideNode.Item; nextNode = scanLine.NextHigh(nextNode)) {
LoadReflectionEvents(nextNode.Item);
}
}
// Remove the obstacle from the Scanline. This comes after the foregoing which is why it's a
// separate function; the RBTree modifies RBNodes, so doing the .Remove at the end of a separate
// function ensures we won't access RBNodes that may no longer contain what we expect.
scanLine.Remove(obstacle.ActiveLowSide, closeVertEvent.Site);
scanLine.Remove(obstacle.ActiveHighSide, closeVertEvent.Site);
}
void ProcessEvent(CloseVertexEvent closeVertEvent) {
// This event closes the obstacle. It removes the ActiveLowSide and ActiveHighSide from the scanline and does
// not add new sides. As above, see comments in OpenVertexEvent and its callees for more detailed explanations.
CreateCloseEventSegmentsAndFindNeighbors(closeVertEvent);
// For reflection, in addition to the delayed lookahead we do in the other *VertexEvents, we need to
// detect pending reflections up to an already loaded obstacle side. This may be transitive; using
// H directions as an example:
// (A). Obstacles A and B lean rightward such that a vertical line can be drawn from the lower side
// of ObstacleA downward to the right of ObstacleB (missing it) and hitting Obstacle C.
// (B). ObstacleC's start point is above the start points of Obstacles A and B.
// (C). ObjectC reflects a Lookahead scan up along the line cited in (A).
// To handle this, whenever we close an object, see if either nbor side spans lookahead scan points.
// If so, then add the events. Otherwise, we know that a new side for those nbor objects, or for
// subsequent higher objects, will be created at some point (unless we run out of obstacles).
// Since we do reflections only in staircase situations, these lookahead events will be discarded
// (because the existing edge would have to have already been processed as an immediate neighbor,
// in order to satisfy the definition of a staircase). See RectilinearTests.ReflectionsDetectedByAlreadyLoadedSide.
// Check the neighbors in both directions for reflection events.
// When querying for lookahead sites for a nborSide, always test the full nborSide range if
// the opposite nborSide reflects upward, because we will have generated a lookahead site for the
// current eventObstacle on that upward-reflecting nborSide. For example, restricting the
// lowNborSide lookahead-site query to the currentEventObstacle.ActiveLowSide would pick up
// any lookahead sites stored on eventObstacle.ActiveLowSide, but would not pick up a lookahead
// site that the current CloseVertexEvent just stored on the highNborSide).
// Fix: Updated this to remove restricted-range as it skips the range that includes the far side of the
// current obstacle when called for neighbors that extend across the top vertex.
var lowNborSide = (HighObstacleSide)LowNeighborSides.LowNeighbor.Item;
var highNborSide = (LowObstacleSide)HighNeighborSides.HighNeighbor.Item;
var obstacle = closeVertEvent.Obstacle;
LoadReflectionEvents(lowNborSide);
LoadReflectionEvents(highNborSide);
// This prepares the object for the second (perpendicular) sweep.
obstacle.Close();
} // end ProcessEvent(CloseVertexEvent)
void ProcessEvent(LowReflectionEvent lowIntEvent) {
// Unlike LowBendVertexEvent we don't update the ActiveLowSide in the obstacle and scanline.
var obstacle = lowIntEvent.Side.Obstacle;
// Add a perpendicular segment from the previous site to the lowNbor intersection, then from
// the lowNbor intersection to the event site.
var lowNborSide = scanLine.NextLow(lowIntEvent.Side).Item as HighObstacleSide;
if (AddPerpendicularReflectionSegment(lowIntEvent, lowIntEvent.Side, lowNborSide)) {
if (AddParallelReflectionSegment(obstacle, lowNborSide, null /*highNborSide*/, lowIntEvent)) {
// We may have just added a reflection that reflects back onto obstacle.ActiveLowSide.
LoadReflectionEvents(obstacle.ActiveLowSide);
}
}
} // end ProcessEvent(LowReflectionEvent)
void ProcessEvent(HighReflectionEvent highIntEvent) {
// Unlike HighBendVertexEvent we don't update the ActiveHighSide in the obstacle and scanline.
var obstacle = highIntEvent.Side.Obstacle;
// Add a perpendicular segment from the previous site to the highNbor intersection, then from
// the highNbor intersection to the event site.
var highNborSide = scanLine.NextHigh(highIntEvent.Side).Item as LowObstacleSide;
if (AddPerpendicularReflectionSegment(highIntEvent, highIntEvent.Side, highNborSide)) {
if (AddParallelReflectionSegment(obstacle, null /*lowNborSide*/, highNborSide, highIntEvent)) {
// We may have just added a reflection that reflects back onto obstacle.ActiveHighSide.
LoadReflectionEvents(obstacle.ActiveHighSide);
}
}
} // end ProcessEvent(HighReflectionEvent)
internal Point MakeInBoundsLocation(Point location) {
double xPos = Math.Max(location.X, ObstacleTree.GraphBox.Left);
double yPos = Math.Max(location.Y, ObstacleTree.GraphBox.Bottom);
return new Point(Math.Min(xPos, ObstacleTree.GraphBox.Right), Math.Min(yPos, ObstacleTree.GraphBox.Top));
}
internal bool IsInBounds(VisibilityVertex vertex) {
return IsInBounds(vertex.Point);
}
internal bool IsInBounds(Point p) {
return PointComparer.Equal(p, MakeInBoundsLocation(p));
}
#region DevTrace
#if DEVTRACE
readonly DevTrace visGraphGenTrace = new DevTrace("Rectilinear_VisGraphGenTrace", "VisGraphGen");
//unused DevTrace VisGraphGenVerify = new DevTrace("Rectilinear_VisGraphGenVerify");
readonly DevTrace scanSegmentDump = new DevTrace("Rectilinear_ScanSegmentDump");
#endif // DEVTRACE
[Conditional("DEVTRACE")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
protected void DevTraceInfoVgGen(int verboseLevel, string format, params object[] args) {
#if DEVTRACE
visGraphGenTrace.WriteLineIf(DevTrace.Level.Info, verboseLevel, format, args);
#endif // DEVTRACE
}
[Conditional("DEVTRACE")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
void DevTraceIfFlatSide(bool isObstacleOpen, Point lowEndpoint, Point highEndpoint) {
#if DEVTRACE
if (lowEndpoint != highEndpoint) {
visGraphGenTrace.WriteLineIf(DevTrace.Level.Info, 2, "IsFlat side on {0}VertexEvent: [{1}{2}]"
, isObstacleOpen ? "Open" : "Close", lowEndpoint, highEndpoint);
}
#endif // DEVTRACE
}
[Conditional("DEVTRACE")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
void DevTraceScanSegmentDump(int verboseLevel) {
#if DEVTRACE
if (scanSegmentDump.IsLevel(verboseLevel)) {
StaticGraphUtility.Test_DumpScanSegments(ObstacleTree, HorizontalScanSegments, VerticalScanSegments);
}
#endif // DEVTRACE
}
#endregion // DevTrace
}
}