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); }
internal void Clear() { this.LowNeighbor = null; this.LowOverlapEnd = null; this.GroupSideInterveningBeforeLowNeighbor = null; this.HighNeighbor = null; this.HighOverlapEnd = null; this.GroupSideInterveningBeforeHighNeighbor = null; }
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 }
internal void Clear() { this.LowNeighbor = null; this.LowOverlapEnd = null; this.GroupSideInterveningBeforeLowNeighbor = null; this.HighNeighbor = null; this.HighOverlapEnd = null; this.GroupSideInterveningBeforeHighNeighbor = null; }
protected override bool InsertParallelReflectionSegment(Point start, Point end, Obstacle eventObstacle, BasicObstacleSide lowNborSide, BasicObstacleSide highNborSide, BasicReflectionEvent action) { // See notes in InsertPerpendicularReflectionSegment for comments about an existing segment. // Here, we call AddSegment which adds the segment and continues the reflection staircase. if (null != ParallelScanSegments.Find(start, end)) { return false; } return AddSegment(start, end, eventObstacle, lowNborSide, highNborSide, action, ScanSegment.ReflectionWeight); }
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); }
protected override bool InsertParallelReflectionSegment(Point start, Point end, Obstacle eventObstacle, BasicObstacleSide lowNborSide, BasicObstacleSide highNborSide, BasicReflectionEvent action) { // See notes in InsertPerpendicularReflectionSegment for comments about an existing segment. // Here, we call AddSegment which adds the segment and continues the reflection staircase. if (null != ParallelScanSegments.Find(start, end)) { return(false); } return(AddSegment(start, end, eventObstacle, lowNborSide, highNborSide, action, ScanSegment.ReflectionWeight)); }
internal void SetSides(Directions dir, RBNode<BasicObstacleSide> neighborNode, RBNode<BasicObstacleSide> overlapEndNode, BasicObstacleSide interveningGroupSide) { if (StaticGraphUtility.IsAscending(dir)) { HighNeighbor = neighborNode; HighOverlapEnd = overlapEndNode; this.GroupSideInterveningBeforeHighNeighbor = interveningGroupSide; return; } LowNeighbor = neighborNode; LowOverlapEnd = overlapEndNode; this.GroupSideInterveningBeforeLowNeighbor = interveningGroupSide; }
private void StoreLookaheadSite(Obstacle eventObstacle, BasicObstacleSide interveningGroupSide, BasicObstacleSide neighborSide, Point siteOnSide) { // For reflections, NeighborSides won't be set, so there won't be an intervening group. Otherwise, // this is on an OpenVertexEvent, so we'll either reflect of the intervening group if any, or neighborSide. if (null == interveningGroupSide) { this.StoreLookaheadSite(eventObstacle, neighborSide, siteOnSide, wantExtreme: false); } else { var siteOnGroup = ScanLineIntersectSide(siteOnSide, interveningGroupSide, this.ScanDirection); this.StoreLookaheadSite(eventObstacle, interveningGroupSide, siteOnGroup, wantExtreme: false); } }
internal void SetSides(Directions dir, RBNode <BasicObstacleSide> neighborNode, RBNode <BasicObstacleSide> overlapEndNode, BasicObstacleSide interveningGroupSide) { if (StaticGraphUtility.IsAscending(dir)) { HighNeighbor = neighborNode; HighOverlapEnd = overlapEndNode; this.GroupSideInterveningBeforeHighNeighbor = interveningGroupSide; return; } LowNeighbor = neighborNode; LowOverlapEnd = overlapEndNode; this.GroupSideInterveningBeforeLowNeighbor = interveningGroupSide; }
// obstacleToIgnore is the event obstacle if we're looking at intersections along its boundary. private bool IntersectionAtSideIsInsideAnotherObstacle(BasicObstacleSide side, Obstacle eventObstacle, Point intersect) { // See if the intersection with an obstacle side is inside another obstacle (that encloses // at least the part of side.Obstacle containing the intersection). This will only happen // if side.Obstacle is overlapped and in the same clump (if it's not the same clump, we must // be hitting it from the outside). if (!side.Obstacle.IsOverlapped) { return(false); } if (!side.Obstacle.IsGroup && !eventObstacle.IsGroup && (side.Obstacle.Clump != eventObstacle.Clump)) { return(false); } return(ObstacleTree.IntersectionIsInsideAnotherObstacle(side.Obstacle, eventObstacle, intersect, ScanDirection)); }
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)); }
// 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()
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); }
// obstacleToIgnore is the event obstacle if we're looking at intersections along its boundary. private bool IntersectionAtSideIsInsideAnotherObstacle(BasicObstacleSide side, Obstacle eventObstacle, Point intersect) { // See if the intersection with an obstacle side is inside another obstacle (that encloses // at least the part of side.Obstacle containing the intersection). This will only happen // if side.Obstacle is overlapped and in the same clump (if it's not the same clump, we must // be hitting it from the outside). if (!side.Obstacle.IsOverlapped) { return false; } if (!side.Obstacle.IsGroup && !eventObstacle.IsGroup && (side.Obstacle.Clump != eventObstacle.Clump)) { return false; } return ObstacleTree.IntersectionIsInsideAnotherObstacle(side.Obstacle, eventObstacle, intersect, ScanDirection); }
} // 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)); }
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; }
protected abstract bool InsertParallelReflectionSegment(Point start, Point end, Obstacle eventObstacle, BasicObstacleSide lowNborSide, BasicObstacleSide highNborSide, BasicReflectionEvent action);
// 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; }
private bool IntersectionAtSideIsInsideAnotherObstacle(BasicObstacleSide side, BasicVertexEvent vertexEvent) { Point intersect = ScanLineIntersectSide(vertexEvent.Site, side); return(IntersectionAtSideIsInsideAnotherObstacle(side, vertexEvent.Obstacle, intersect)); }
// As described in the document, we currently don't create ScanSegments where a flat top/bottom boundary may have // intervals that are embedded within overlapped segments: // obstacle1 | |obstacle2 | obstacle3 | | obstacle4 | obstacle2| | // | +-----------|===========|??????????|===========|----------+ | obstacle5 // ...__________| |___________| |___________| |__________... // Here, there will be no ScanSegment created at ??? along the border of obstacle2 between obstacle3 // and obstacle4. This is not a concern because that segment is useless anyway; a path from outside // obstacle2 will not be able to see it unless there is another obstacle in that gap, and then that // obstacle's side-derived ScanSegments will create non-overlapped edges; and there are already edges // from the upper/lower extreme vertices of obstacle 3 and obstacle4 to ensure a connected graph. // If there is a routing from an obstacle outside obstacle2 to one embedded within obstacle2, the // port visibility will create the necessary edges. // // We don't try to determine nesting depth and create different VisibilityEdge weights to prevent spurious // nested-obstacle crossings; we just care about overlapped vs. not-overlapped. // If this changes, we would have to: Find the overlap depth at the lowNborSide intersection, // then increment/decrement according to side type as we move from low to high, then create a different // ScanSegment at each obstacle-side crossing, making ScanSegment.IsOverlapped a depth instead of bool. // Then pass that depth through to VisibilityEdge as an increased weight. (This would also automatically // handle the foregoing situation of non-overlapped intervals in the middle of a flat top/bottom border, // not that it would really gain anything). void CreateScanSegments(Obstacle obstacle, HighObstacleSide lowNborSide, BasicObstacleSide lowOverlapSide, BasicObstacleSide highOverlapSide, LowObstacleSide highNborSide, BasicVertexEvent vertexEvent) { // If we have either of the high/low OverlapSides, we'll need to see if they're inside // another obstacle. If not, they end the overlap. if ((null == highOverlapSide) || IntersectionAtSideIsInsideAnotherObstacle(highOverlapSide, vertexEvent)) { highOverlapSide = highNborSide; } if ((null == lowOverlapSide) || IntersectionAtSideIsInsideAnotherObstacle(lowOverlapSide, vertexEvent)) { lowOverlapSide = lowNborSide; } // There may be up to 3 segments; for a simple diagram, |# means low-side border of // obstacle '#' and #| means high-side, with 'v' meaning our event vertex. Here are // the two cases where we create a single non-overlapped ScanSegment (from the Low // side in the diagram, but the same logic works for the High side). // - non-overlapped: 1| v |2 // ...---+ +---... // - non-overlapped to an "inner" highNbor on a flat border: 1| vLow |2 vHigh // ...---+ +-----+=========... // This may be the low side of a flat bottom or top border, so lowNbor or highNbor // may be in the middle of the border. Point lowNborIntersect = ScanLineIntersectSide(vertexEvent.Site, lowNborSide); Point highNborIntersect = ScanLineIntersectSide(vertexEvent.Site, highNborSide); bool lowNborEndpointIsOverlapped = IntersectionAtSideIsInsideAnotherObstacle(lowNborSide, vertexEvent.Obstacle /*obstacleToIgnore*/, lowNborIntersect); if (!lowNborEndpointIsOverlapped && (lowNborSide == lowOverlapSide)) { // Nothing is overlapped so create one segment. AddSegment(lowNborIntersect, highNborIntersect, obstacle, lowNborSide, highNborSide, vertexEvent, ScanSegment.NormalWeight); return; } // Here are the different interval combinations for overlapped cases. // - non-overlapped, overlapped: 1| |2 v |3 // ...---+ +------+===... // - non-overlapped, overlapped, non-overlapped: 1| |2 v 2| |3 // ==+ +-------+ +--... // - overlapped: |1 2| v |3 ...1| // ...---+====+-----+===...-+ // - overlapped, non-overlapped: |1 2| v 1| |3 // ...---+====+------+ +---... // We will not start overlapped and then go to non-overlapped and back to overlapped, // because we would have found the overlap-ending/beginning sides as nearer neighbors. // Get the other intersections we'll want. Point highOverlapIntersect = (highOverlapSide == highNborSide) ? highNborIntersect : ScanLineIntersectSide(vertexEvent.Site, highOverlapSide); Point lowOverlapIntersect = (lowOverlapSide == lowNborSide) ? lowNborIntersect : ScanLineIntersectSide(vertexEvent.Site, lowOverlapSide); // Create the segments. if (!lowNborEndpointIsOverlapped) { // First interval is non-overlapped; there is a second overlapped interval, and may be a // third non-overlapping interval if another obstacle surrounded this vertex. AddSegment(lowNborIntersect, lowOverlapIntersect, obstacle, lowNborSide, lowOverlapSide, vertexEvent, ScanSegment.NormalWeight); AddSegment(lowOverlapIntersect, highOverlapIntersect, obstacle, lowOverlapSide, highOverlapSide, vertexEvent, ScanSegment.OverlappedWeight); if (highOverlapSide != highNborSide) { AddSegment(highOverlapIntersect, highNborIntersect, obstacle, highOverlapSide, highNborSide, vertexEvent, ScanSegment.NormalWeight); } } else { // Starts off overlapped so ignore lowOverlapSide. AddSegment(lowNborIntersect, highOverlapIntersect, obstacle, lowNborSide, highOverlapSide, vertexEvent, ScanSegment.OverlappedWeight); if (highOverlapSide != highNborSide) { AddSegment(highOverlapIntersect, highNborIntersect, obstacle, highOverlapSide, highNborSide, vertexEvent, ScanSegment.NormalWeight); } } }
private void StoreLookaheadSite(Obstacle eventObstacle, BasicObstacleSide interveningGroupSide, BasicObstacleSide neighborSide, Point siteOnSide) { // For reflections, NeighborSides won't be set, so there won't be an intervening group. Otherwise, // this is on an OpenVertexEvent, so we'll either reflect of the intervening group if any, or neighborSide. if (null == interveningGroupSide) { this.StoreLookaheadSite(eventObstacle, neighborSide, siteOnSide, wantExtreme: false); } else { var siteOnGroup = ScanLineIntersectSide(siteOnSide, interveningGroupSide, this.ScanDirection); this.StoreLookaheadSite(eventObstacle, interveningGroupSide, siteOnGroup, wantExtreme: false); } }
private bool IntersectionAtSideIsInsideAnotherObstacle(BasicObstacleSide side, BasicVertexEvent vertexEvent) { Point intersect = ScanLineIntersectSide(vertexEvent.Site, side); return IntersectionAtSideIsInsideAnotherObstacle(side, vertexEvent.Obstacle, intersect); }
// Return value is whether or not we added a new segment. bool AddSegment(Point start, Point end, Obstacle eventObstacle , BasicObstacleSide lowNborSide, BasicObstacleSide highNborSide , SweepEvent action, double weight) { DevTraceInfoVgGen(1, "Adding Segment [{0} -> {1} {2}] weight {3}", start, end, weight); DevTraceInfoVgGen(2, " side {0}", lowNborSide); DevTraceInfoVgGen(2, " -> side {0}", highNborSide); if (PointComparer.Equal(start, end)) { return false; } // See if the new segment subsumes or can be subsumed by the last one. gbcList may be null. PointAndCrossingsList gbcList = CurrentGroupBoundaryCrossingMap.GetOrderedListBetween(start, end); bool extendStart, extendEnd; bool wasSubsumed = ScanSegment.Subsume(ref hintScanSegment, start, end, weight, gbcList, ScanDirection , ParallelScanSegments, out extendStart, out extendEnd); if (!wasSubsumed) { Debug.Assert((weight != ScanSegment.ReflectionWeight) || (ParallelScanSegments.Find(start, end) == null), "Reflection segments already in the ScanSegmentTree should should have been detected before calling AddSegment"); hintScanSegment = ParallelScanSegments.InsertUnique(new ScanSegment(start, end, weight, gbcList)).Item; } else if (weight == ScanSegment.ReflectionWeight) { // Do not continue this; it is probably a situation where a side is at a tiny angle from the axis, // resulting in an initial reflection segment that is parallel and very close to the extreme-vertex-derived // segment, so as the staircase progresses they eventually converge due to floating-point rounding. // See RectilinearFilesTest.ReflectionStaircasesConverge. return false; } // Do reflections only if the new segment is not overlapped. if (ScanSegment.OverlappedWeight != weight) { // If these fire, it's probably an indication that isOverlapped is not correctly set // and one of the neighbors is an OverlapSide from CreateScanSegments. Debug.Assert(lowNborSide is HighObstacleSide, "lowNbor is not HighObstacleSide"); Debug.Assert(highNborSide is LowObstacleSide, "highNbor is not LowObstacleSide"); // If we are closing the obstacle then the initial Obstacles of the reflections (the ones it // will bounce between) are the opposite neighbors. Otherwise, the OpenVertexEvent obstacle // is the ReflectionEvent initial obstacle. if (action is CloseVertexEvent) { // If both neighbor sides reflect upward, they can't intersect, so we don't need // to store a lookahead site (if neither reflect upward, StoreLookaheadSite no-ops). if (!SideReflectsUpward(lowNborSide) || !SideReflectsUpward(highNborSide)) { // Try to store both; only one will "take" (for the upward-reflecting side). // The initial Obstacle is the opposite neighbor. if (extendStart) { this.StoreLookaheadSite(highNborSide.Obstacle, lowNborSide, start, wantExtreme:false); } if (extendEnd) { this.StoreLookaheadSite(lowNborSide.Obstacle, highNborSide, end, wantExtreme: false); } } } else { if (extendStart) { StoreLookaheadSite(eventObstacle, LowNeighborSides.GroupSideInterveningBeforeLowNeighbor, lowNborSide, start); } if (extendEnd) { StoreLookaheadSite(eventObstacle, HighNeighborSides.GroupSideInterveningBeforeHighNeighbor, highNborSide, end); } } } DevTraceInfoVgGen(2, "HintScanSegment {0}{1}", hintScanSegment, wasSubsumed ? " (subsumed)" : ""); DevTrace_DumpScanSegmentsDuringAdd(3); return true; }
} // end StoreLookaheadSite() // Load any lookahead scan ray intersections with a side we've just added. protected void LoadReflectionEvents(BasicObstacleSide sideToQueue) { LoadReflectionEvents(sideToQueue, sideToQueue); }
// Return value is whether or not we added a new segment. bool AddSegment(Point start, Point end, Obstacle eventObstacle , BasicObstacleSide lowNborSide, BasicObstacleSide highNborSide , SweepEvent action, double weight) { DevTraceInfoVgGen(1, "Adding Segment [{0} -> {1} {2}] weight {3}", start, end, weight); DevTraceInfoVgGen(2, " side {0}", lowNborSide); DevTraceInfoVgGen(2, " -> side {0}", highNborSide); if (PointComparer.Equal(start, end)) { return(false); } // See if the new segment subsumes or can be subsumed by the last one. gbcList may be null. PointAndCrossingsList gbcList = CurrentGroupBoundaryCrossingMap.GetOrderedListBetween(start, end); bool extendStart, extendEnd; bool wasSubsumed = ScanSegment.Subsume(ref hintScanSegment, start, end, weight, gbcList, ScanDirection , ParallelScanSegments, out extendStart, out extendEnd); if (!wasSubsumed) { Debug.Assert((weight != ScanSegment.ReflectionWeight) || (ParallelScanSegments.Find(start, end) == null), "Reflection segments already in the ScanSegmentTree should should have been detected before calling AddSegment"); hintScanSegment = ParallelScanSegments.InsertUnique(new ScanSegment(start, end, weight, gbcList)).Item; } else if (weight == ScanSegment.ReflectionWeight) { // Do not continue this; it is probably a situation where a side is at a tiny angle from the axis, // resulting in an initial reflection segment that is parallel and very close to the extreme-vertex-derived // segment, so as the staircase progresses they eventually converge due to floating-point rounding. // See RectilinearFilesTest.ReflectionStaircasesConverge. return(false); } // Do reflections only if the new segment is not overlapped. if (ScanSegment.OverlappedWeight != weight) { // If these fire, it's probably an indication that isOverlapped is not correctly set // and one of the neighbors is an OverlapSide from CreateScanSegments. Debug.Assert(lowNborSide is HighObstacleSide, "lowNbor is not HighObstacleSide"); Debug.Assert(highNborSide is LowObstacleSide, "highNbor is not LowObstacleSide"); // If we are closing the obstacle then the initial Obstacles of the reflections (the ones it // will bounce between) are the opposite neighbors. Otherwise, the OpenVertexEvent obstacle // is the ReflectionEvent initial obstacle. if (action is CloseVertexEvent) { // If both neighbor sides reflect upward, they can't intersect, so we don't need // to store a lookahead site (if neither reflect upward, StoreLookaheadSite no-ops). if (!SideReflectsUpward(lowNborSide) || !SideReflectsUpward(highNborSide)) { // Try to store both; only one will "take" (for the upward-reflecting side). // The initial Obstacle is the opposite neighbor. if (extendStart) { this.StoreLookaheadSite(highNborSide.Obstacle, lowNborSide, start, wantExtreme: false); } if (extendEnd) { this.StoreLookaheadSite(lowNborSide.Obstacle, highNborSide, end, wantExtreme: false); } } } else { if (extendStart) { StoreLookaheadSite(eventObstacle, LowNeighborSides.GroupSideInterveningBeforeLowNeighbor, lowNborSide, start); } if (extendEnd) { StoreLookaheadSite(eventObstacle, HighNeighborSides.GroupSideInterveningBeforeHighNeighbor, highNborSide, end); } } } DevTraceInfoVgGen(2, "HintScanSegment {0}{1}", hintScanSegment, wasSubsumed ? " (subsumed)" : ""); DevTrace_DumpScanSegmentsDuringAdd(3); return(true); }
// 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(); }
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; }
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); }
} // end EnqueueBottomVertexEvents internal bool IsFlat(BasicObstacleSide side) { return ScanDirection.IsFlat(side); }
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)); } }
internal bool IsPerpendicular(BasicObstacleSide side) { // If it's perpendicular we won't generate reflections. return ScanDirection.IsPerpendicular(side); }
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); }
// 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); }
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)
// As described in the document, we currently don't create ScanSegments where a flat top/bottom boundary may have // intervals that are embedded within overlapped segments: // obstacle1 | |obstacle2 | obstacle3 | | obstacle4 | obstacle2| | // | +-----------|===========|??????????|===========|----------+ | obstacle5 // ...__________| |___________| |___________| |__________... // Here, there will be no ScanSegment created at ??? along the border of obstacle2 between obstacle3 // and obstacle4. This is not a concern because that segment is useless anyway; a path from outside // obstacle2 will not be able to see it unless there is another obstacle in that gap, and then that // obstacle's side-derived ScanSegments will create non-overlapped edges; and there are already edges // from the upper/lower extreme vertices of obstacle 3 and obstacle4 to ensure a connected graph. // If there is a routing from an obstacle outside obstacle2 to one embedded within obstacle2, the // port visibility will create the necessary edges. // // We don't try to determine nesting depth and create different VisibilityEdge weights to prevent spurious // nested-obstacle crossings; we just care about overlapped vs. not-overlapped. // If this changes, we would have to: Find the overlap depth at the lowNborSide intersection, // then increment/decrement according to side type as we move from low to high, then create a different // ScanSegment at each obstacle-side crossing, making ScanSegment.IsOverlapped a depth instead of bool. // Then pass that depth through to VisibilityEdge as an increased weight. (This would also automatically // handle the foregoing situation of non-overlapped intervals in the middle of a flat top/bottom border, // not that it would really gain anything). void CreateScanSegments(Obstacle obstacle, HighObstacleSide lowNborSide, BasicObstacleSide lowOverlapSide, BasicObstacleSide highOverlapSide, LowObstacleSide highNborSide, BasicVertexEvent vertexEvent) { // If we have either of the high/low OverlapSides, we'll need to see if they're inside // another obstacle. If not, they end the overlap. if ((null == highOverlapSide) || IntersectionAtSideIsInsideAnotherObstacle(highOverlapSide, vertexEvent)) { highOverlapSide = highNborSide; } if ((null == lowOverlapSide) || IntersectionAtSideIsInsideAnotherObstacle(lowOverlapSide, vertexEvent)) { lowOverlapSide = lowNborSide; } // There may be up to 3 segments; for a simple diagram, |# means low-side border of // obstacle '#' and #| means high-side, with 'v' meaning our event vertex. Here are // the two cases where we create a single non-overlapped ScanSegment (from the Low // side in the diagram, but the same logic works for the High side). // - non-overlapped: 1| v |2 // ...---+ +---... // - non-overlapped to an "inner" highNbor on a flat border: 1| vLow |2 vHigh // ...---+ +-----+=========... // This may be the low side of a flat bottom or top border, so lowNbor or highNbor // may be in the middle of the border. Point lowNborIntersect = ScanLineIntersectSide(vertexEvent.Site, lowNborSide); Point highNborIntersect = ScanLineIntersectSide(vertexEvent.Site, highNborSide); bool lowNborEndpointIsOverlapped = IntersectionAtSideIsInsideAnotherObstacle(lowNborSide, vertexEvent.Obstacle /*obstacleToIgnore*/, lowNborIntersect); if (!lowNborEndpointIsOverlapped && (lowNborSide == lowOverlapSide)) { // Nothing is overlapped so create one segment. AddSegment(lowNborIntersect, highNborIntersect, obstacle, lowNborSide, highNborSide, vertexEvent, ScanSegment.NormalWeight); return; } // Here are the different interval combinations for overlapped cases. // - non-overlapped, overlapped: 1| |2 v |3 // ...---+ +------+===... // - non-overlapped, overlapped, non-overlapped: 1| |2 v 2| |3 // ==+ +-------+ +--... // - overlapped: |1 2| v |3 ...1| // ...---+====+-----+===...-+ // - overlapped, non-overlapped: |1 2| v 1| |3 // ...---+====+------+ +---... // We will not start overlapped and then go to non-overlapped and back to overlapped, // because we would have found the overlap-ending/beginning sides as nearer neighbors. // Get the other intersections we'll want. Point highOverlapIntersect = (highOverlapSide == highNborSide) ? highNborIntersect : ScanLineIntersectSide(vertexEvent.Site, highOverlapSide); Point lowOverlapIntersect = (lowOverlapSide == lowNborSide) ? lowNborIntersect : ScanLineIntersectSide(vertexEvent.Site, lowOverlapSide); // Create the segments. if (!lowNborEndpointIsOverlapped) { // First interval is non-overlapped; there is a second overlapped interval, and may be a // third non-overlapping interval if another obstacle surrounded this vertex. AddSegment(lowNborIntersect, lowOverlapIntersect, obstacle, lowNborSide, lowOverlapSide, vertexEvent, ScanSegment.NormalWeight); AddSegment(lowOverlapIntersect, highOverlapIntersect, obstacle, lowOverlapSide, highOverlapSide, vertexEvent, ScanSegment.OverlappedWeight); if (highOverlapSide != highNborSide) { AddSegment(highOverlapIntersect, highNborIntersect, obstacle, highOverlapSide, highNborSide, vertexEvent, ScanSegment.NormalWeight); } } else { // Starts off overlapped so ignore lowOverlapSide. AddSegment(lowNborIntersect, highOverlapIntersect, obstacle, lowNborSide, highOverlapSide, vertexEvent, ScanSegment.OverlappedWeight); if (highOverlapSide != highNborSide) { AddSegment(highOverlapIntersect, highNborIntersect, obstacle, highOverlapSide, highNborSide, vertexEvent, ScanSegment.NormalWeight); } } }