void ScanIntersect(ScanSegment hSeg) { // Find the VSeg in the scanline with the lowest X-intersection with HSeg, then iterate // all VSegs in the scan line after that until we leave the HSeg range. // We only use FindFirstHSeg in this routine, to find the first satisfying node, // so we don't care that we leave leftovers in it. findFirstHSeg = hSeg; RBNode <ScanSegment> segNode = verticalSegmentsScanLine.FindFirst(findFirstPred); for (; null != segNode; segNode = verticalSegmentsScanLine.Next(segNode)) { ScanSegment vSeg = segNode.Item; if (1 == PointComparer.Compare(vSeg.Start.X, hSeg.End.X)) { break; // Out of HSeg range } VisibilityVertex newVertex = visGraph.AddVertex(new Point(vSeg.Start.X, hSeg.Start.Y)); // HSeg has just opened so if we are overlapped and newVertex already existed, // it was because we just closed a previous HSeg or VSeg and are now opening one // whose Start is the same as previous. So we may be appending a vertex that // is already the *Seg.HighestVisibilityVertex, which will be a no-op. Otherwise // this will add a (possibly Overlapped)VisibilityEdge in the *Seg direction. hSeg.AppendVisibilityVertex(visGraph, newVertex); vSeg.AppendVisibilityVertex(visGraph, newVertex); } // endforeach scanline VSeg in range OnSegmentClose(hSeg); }
/// <summary> /// For ordering V segments in the scanline by X. /// </summary> /// <param name="first"></param> /// <param name="second"></param> /// <returns></returns> public int Compare(ScanSegment first, ScanSegment second) { if (first == second) { return(0); } if (first == null) { return(-1); } if (second == null) { return(1); } // Note: Unlike the ScanSegment-generating scanline, this scanline has no slope // calculations so no additional rounding error is introduced. int cmp = PointComparer.Compare(first.Start.X, second.Start.X); // Separate segments may join at Start and End due to overlap, so compare the Y positions; // the Close (lowest Y) comes before the Open. if (0 == cmp) { cmp = PointComparer.Compare(first.Start.Y, second.Start.Y); } return(cmp); }
void OnSegmentClose(ScanSegment seg) { seg.OnSegmentIntersectorEnd(visGraph); if (null == seg.LowestVisibilityVertex) { segmentsWithoutVisibility.Add(seg); } }
void ScanInsert(ScanSegment seg) { Debug.Assert(null == this.verticalSegmentsScanLine.Find(seg), "seg already exists in the rbtree"); // RBTree's internal operations on insert/remove etc. mean the node can't cache the // RBNode returned by insert(); instead we must do find() on each call. But we can // use the returned node to get predecessor/successor. verticalSegmentsScanLine.Insert(seg); }
internal void AppendScanSegment(ScanSegment segment) { if (null == this.FirstSegment) { this.FirstSegment = segment; } else { // Note: segment.Start may != Current.End due to skipping internal ScanSegment creation for non-overlapped obstacles. this.CurrentSegment.NextSegment = segment; } this.CurrentSegment = segment; }
internal Point GetIntersection(ScanSegment seg) { return StaticGraphUtility.SegmentIntersection(this, seg); }
void OnSegmentOpen(ScanSegment seg) { seg.OnSegmentIntersectorBegin(visGraph); }
// If we have collinear segments, then we may be able to just update the previous one // instead of growing the ScanSegmentTree. // - For multiple collinear OpenVertexEvents, neighbors to the high side have not yet // been seen, so a segment is created that spans the lowest and highest neighbors. // A subsequent collinear OpenVertexEvent will be to the high side and will add a // subsegment of that segment, so we subsume it into LastAddedSegment. // - For multiple collinear CloseVertexEvents, closing neighbors to the high side are // still open, so a segment is created from the lowest neighbor to the next-highest // collinear obstacle to be closed. When that next-highest CloseVertexEvent is // encountered, it will extend LastAddedSegment. // - For multiple collinear mixed Open and Close events, we'll do all Opens first, // followed by all closes (per EventQueue opening), so we may add multiple discrete // segments, which ScanSegmentTree will merge. internal static bool Subsume(ref ScanSegment seg, Point newStart, Point newEnd, double weight, PointAndCrossingsList gbcList, ScanDirection scanDir, ScanSegmentTree tree, out bool extendStart, out bool extendEnd) { // Initialize these to the non-subsumed state; the endpoints were extended (or on a // different line). extendStart = true; extendEnd = true; if (null == seg) { return(false); } // If they don't overlap (including touching at an endpoint), we don't subsume. if (!StaticGraphUtility.IntervalsOverlap(seg.Start, seg.End, newStart, newEnd)) { return(false); } // If the overlapped-ness isn't the same, we don't subsume. ScanSegmentTree::MergeSegments // will mark that the low-to-high direction needs a VisibilityVertex to link the two segments. // These may differ by more than Curve.DistanceEpsilon in the case of reflection lookahead // segments collinear with vertex-derived segments, so have a looser tolerance here and we'll // adjust the segments in ScanSegmentTree.MergeSegments. if (seg.Weight != weight) { if ((seg.Start == newStart) && (seg.End == newEnd)) { // This is probably because of a rounding difference by one DistanceEpsilon reporting being // inside an obstacle vs. the scanline intersection calculation side-ordering. // Test is RectilinearFileTests.Overlap_Rounding_Vertex_Intersects_Side. seg.Weight = Math.Min(seg.Weight, weight); return(true); } // In the case of groups, we go through the group boundary; this may coincide with a // reflection segment. RectilinearFileTests.ReflectionSubsumedBySegmentExitingGroup. Debug.Assert((seg.Weight == OverlappedWeight) == (weight == OverlappedWeight) || ApproximateComparer.CloseIntersections(seg.End, newStart) || ApproximateComparer.CloseIntersections(seg.Start, newEnd) , "non-equal overlap-mismatched ScanSegments overlap by more than just Start/End"); return(false); } // Subsume the input segment. Return whether the start/end points were extended (newStart // is before this.Start, or newEnd is after this.End), so the caller can generate reflections // and so we can merge group border crossings. extendStart = (-1 == scanDir.CompareScanCoord(newStart, seg.Start)); extendEnd = (1 == scanDir.CompareScanCoord(newEnd, seg.End)); if (extendStart || extendEnd) { // We order by start and end so need to replace this in the tree regardless of which end changes. tree.Remove(seg); seg.startPoint = scanDir.Min(seg.Start, newStart); seg.endPoint = scanDir.Max(seg.End, newEnd); seg = tree.InsertUnique(seg).Item; seg.MergeGroupBoundaryCrossingList(gbcList); } return(true); }
internal override void Clear() { base.Clear(); hintScanSegment = null; }
internal override void InitializeEventQueue(ScanDirection scanDir) { base.InitializeEventQueue(scanDir); hintScanSegment = null; }
// 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); }
internal bool IntersectsSegment(ScanSegment seg) { return StaticGraphUtility.SegmentsIntersect(this, seg); }
private VisibilityEdge AddEdgeToClosestSegmentEnd(ScanSegment scanSeg, VisibilityVertex segsegVertex, double weight) { // FindOrAddEdge will walk until it finds the minimal bracketing vertices. if (PointComparer.IsPureLower(scanSeg.HighestVisibilityVertex.Point, segsegVertex.Point)) { return this.TransUtil.FindOrAddEdge(scanSeg.HighestVisibilityVertex, segsegVertex, weight); } if (PointComparer.IsPureLower(segsegVertex.Point, scanSeg.LowestVisibilityVertex.Point)) { return this.TransUtil.FindOrAddEdge(segsegVertex, scanSeg.LowestVisibilityVertex, weight); } return this.TransUtil.FindOrAddEdge(scanSeg.LowestVisibilityVertex, segsegVertex); }
internal bool MoveNext() { this.CurrentSegment = this.CurrentSegment.NextSegment; return(this.HasCurrent); }
// 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; }
/// <summary> /// Restores state between intersection passes. /// </summary> internal void ResetForIntersections() { Debug.Assert(null != this.FirstSegment, "Empty ScanSegmentVectorItem"); this.CurrentSegment = this.FirstSegment; }
internal bool MoveNext() { this.CurrentSegment = this.CurrentSegment.NextSegment; return this.HasCurrent; }
internal bool IntersectsSegment(ScanSegment seg) { return(StaticGraphUtility.SegmentsIntersect(this, seg)); }
void ScanRemove(ScanSegment seg) { verticalSegmentsScanLine.Remove(seg); }
bool IsVSegInHSegRange(ScanSegment v) { return(PointComparer.Compare(v.Start.X, findFirstHSeg.Start.X) >= 0); }
internal Point GetIntersection(ScanSegment seg) { return(StaticGraphUtility.SegmentIntersection(this, seg)); }
internal SegEvent(SegEventType eventType, ScanSegment seg) { EventType = eventType; Segment = seg; }
private VisibilityEdge FindOrCreateNearestPerpEdgeFromNearestPerpSegment(Point pointLocation, ScanSegment scanSeg, Point edgeIntersect, double weight, out VisibilityVertex targetVertex) { // Given: a ScanSegment scanSeg perpendicular to pointLocation->edgeIntersect and containing edgeIntersect. // To find: a VisibilityEdge perpendicular to pointLocation->edgeIntersect which may be on scanSeg, or may // be closer to pointLocation than the passed edgeIntersect is. // Since there may be TransientEdges between pointLocation and edgeIntersect, we start by finding // a scanSeg-intersecting (i.e. parallel to pointLocation->edgeIntersect) ScanSegment, then starting from // the intersection of those segments, walk the VisibilityGraph until we find the closest VisibilityEdge // perpendicular to pointLocation->edgeIntersect. If there is a vertex on that edge collinear to // pointLocation->edgeIntersect, return the edge for which it is Source, else split the edge. // If there is already a vertex at edgeIntersect, we do not need to look for the intersecting ScanSegment. VisibilityVertex segsegVertex = VisGraph.FindVertex(edgeIntersect); if (null == segsegVertex) { var edge = this.FindOrCreateSegmentIntersectionVertexAndAssociatedEdge(pointLocation, edgeIntersect, scanSeg, weight, out segsegVertex, out targetVertex); if (edge != null) { return edge; } } else if (PointComparer.Equal(pointLocation, edgeIntersect)) { // The initial pointLocation was on scanSeg at an existing vertex so return an edge // from that vertex along scanSeg. Look in both directions in case of dead ends. targetVertex = segsegVertex; return TransUtil.FindNextEdge(targetVertex, scanSeg.ScanDirection.Direction) ?? TransUtil.FindNextEdge(targetVertex, CompassVector.OppositeDir(scanSeg.ScanDirection.Direction)); } // pointLocation is not on the initial scanSeg, so see if there is a transient edge between // pointLocation and edgeIntersect. edgeIntersect == segsegVertex.Point if pointLocation is // collinear with intSegBefore (pointLocation is before or after intSegBefore's VisibilityVertices). Directions dirTowardLocation = PointComparer.GetPureDirection(edgeIntersect, pointLocation); Directions perpDir = PointComparer.GetDirections(segsegVertex.Point, pointLocation); if (dirTowardLocation == perpDir) { // intSegBefore is collinear with pointLocation so walk to the vertex closest to pointLocation. VisibilityVertex bracketTarget; TransientGraphUtility.FindBracketingVertices(segsegVertex, pointLocation, dirTowardLocation , out targetVertex, out bracketTarget); // Return an edge. Look in both directions in case of dead ends. return TransUtil.FindNextEdge(targetVertex, CompassVector.RotateLeft(dirTowardLocation)) ?? TransUtil.FindNextEdge(targetVertex, CompassVector.RotateRight(dirTowardLocation)); } // Now make perpDir have only the perpendicular component. perpDir &= ~dirTowardLocation; // if this is Directions. None, pointLocation == edgeIntersect StaticGraphUtility.Assert(Directions. None != perpDir , "pointLocation == initial segsegVertex.Point should already have exited", ObstacleTree, VisGraph); // Other TransientVE edge chains may have been added between the control point and the // ScanSegment (which is always non-transient), and they may have split ScanSegment VEs. // Fortunately we know we'll always have all transient edge chains extended to or past any // control point (due to LimitRectangle), so we can just move up lowestIntSeg toward // pointLocation, updating segsegVertex and edgeIntersect. There are 3 possibilities: // - location is not on an edge - the usual case, we just create an edge perpendicular // to an edge on scanSeg, splitting that scanSeg edge in the process. // - location is on a VE that is parallel to scanSeg. This is essentially the same thing // but we don't need the first perpendicular edge to scanSeg. // - location is on a VE that is perpendicular to scanSeg. In that case the vertex on ScanSeg // already exists; TransUtil.FindOrAddEdge just returns the edge starting at that intersection. // FreePoint tests of this are in RectilinearTests.FreePortLocationRelativeToTransientVisibilityEdges*. VisibilityEdge perpendicularEdge = TransUtil.FindNearestPerpendicularOrContainingEdge(segsegVertex, perpDir, pointLocation); if (null == perpendicularEdge) { // Dead end; we're above the highest point at which there is an intersection of scanSeg. // Create a new vertex and edge higher than the ScanSegment's HighestVisibilityVertex // if that doesn't cross an obstacle (if we are between two ScanSegment dead-ends, we may). // We hit this in RectilinearFileTests.Nudger_Many_Paths_In_Channel and .Nudger_Overlap*. StaticGraphUtility.Assert(edgeIntersect > scanSeg.HighestVisibilityVertex.Point , "edgeIntersect is not > scanSeg.HighestVisibilityVertex", ObstacleTree, VisGraph); targetVertex = TransUtil.AddVertex(edgeIntersect); return TransUtil.FindOrAddEdge(targetVertex, scanSeg.HighestVisibilityVertex, scanSeg.Weight); } // We have an intersecting perp edge, which may be on the original scanSeg or closer to pointLocation. // Get one of its vertices and re-find the intersection on it (it doesn't matter which vertex of the // edge we use, but for consistency use the "lower in perpDir" one). segsegVertex = StaticGraphUtility.GetVertex(perpendicularEdge, CompassVector.OppositeDir(perpDir)); edgeIntersect = StaticGraphUtility.SegmentIntersection(pointLocation, edgeIntersect, segsegVertex.Point); // By this point we've verified there's no intervening Transient edge, so if we have an identical // point, we're done. if (PointComparer.Equal(segsegVertex.Point, edgeIntersect)) { targetVertex = segsegVertex; return TransUtil.FindNextEdge(segsegVertex, perpDir); } // The targetVertex doesn't exist; this will split the edge and add it. targetVertex = TransUtil.FindOrAddVertex(edgeIntersect); return TransUtil.FindOrAddEdge(segsegVertex, targetVertex, weight); }
private VisibilityEdge FindOrCreateSegmentIntersectionVertexAndAssociatedEdge(Point pointLocation, Point edgeIntersect, ScanSegment scanSeg, double weight, out VisibilityVertex segsegVertex, out VisibilityVertex targetVertex) { ScanSegmentTree intersectingSegments = scanSeg.IsVertical ? this.HScanSegments : this.VScanSegments; ScanSegment intSegBefore = intersectingSegments.FindHighestIntersector(scanSeg.Start, edgeIntersect); if (null == intSegBefore) { // Dead end; we're below the lowest point at which there is an intersection of scanSeg. // Create a new vertex and edge lower than the ScanSegment's LowestVisibilityVertex. // Test: RectilinearFileTests.Overlap_Rotate_SplicePort_FreeObstaclePorts. segsegVertex = null; targetVertex = this.TransUtil.AddVertex(edgeIntersect); return this.TransUtil.FindOrAddEdge(targetVertex, scanSeg.LowestVisibilityVertex, scanSeg.Weight); } // Get the VisibilityVertex at the intersection of the two segments we just found; // edgeIntersect is between that vertex and another on the segment, and we'll split // the edge between those two vertices (or find one nearer to walk to). Point segsegIntersect = StaticGraphUtility.SegmentIntersection(scanSeg, intSegBefore); segsegVertex = this.VisGraph.FindVertex(segsegIntersect); if (null == segsegVertex) { // This happens only for UseSparseVisibilityGraph; in that case we must create the // intersection vertex in the direction of both segments so we can start walking. segsegVertex = this.TransUtil.AddVertex(segsegIntersect); var newEdge = this.AddEdgeToClosestSegmentEnd(scanSeg, segsegVertex, scanSeg.Weight); this.AddEdgeToClosestSegmentEnd(intSegBefore, segsegVertex, intSegBefore.Weight); if (PointComparer.Equal(segsegVertex.Point, edgeIntersect)) { targetVertex = segsegVertex; return newEdge; } } if (PointComparer.Equal(pointLocation, edgeIntersect)) { // The initial pointLocation was on scanSeg and we had to create a new vertex for it, // so we'll find or create (by splitting) the edge on scanSeg that contains pointLocation. targetVertex = this.TransUtil.FindOrAddVertex(edgeIntersect); return this.TransUtil.FindOrAddEdge(segsegVertex, targetVertex, weight); } targetVertex = null; return null; }
// If we have collinear segments, then we may be able to just update the previous one // instead of growing the ScanSegmentTree. // - For multiple collinear OpenVertexEvents, neighbors to the high side have not yet // been seen, so a segment is created that spans the lowest and highest neighbors. // A subsequent collinear OpenVertexEvent will be to the high side and will add a // subsegment of that segment, so we subsume it into LastAddedSegment. // - For multiple collinear CloseVertexEvents, closing neighbors to the high side are // still open, so a segment is created from the lowest neighbor to the next-highest // collinear obstacle to be closed. When that next-highest CloseVertexEvent is // encountered, it will extend LastAddedSegment. // - For multiple collinear mixed Open and Close events, we'll do all Opens first, // followed by all closes (per EventQueue opening), so we may add multiple discrete // segments, which ScanSegmentTree will merge. internal static bool Subsume(ref ScanSegment seg, Point newStart, Point newEnd, double weight, PointAndCrossingsList gbcList, ScanDirection scanDir, ScanSegmentTree tree, out bool extendStart, out bool extendEnd) { // Initialize these to the non-subsumed state; the endpoints were extended (or on a // different line). extendStart = true; extendEnd = true; if (null == seg) { return false; } // If they don't overlap (including touching at an endpoint), we don't subsume. if (!StaticGraphUtility.IntervalsOverlap(seg.Start, seg.End, newStart, newEnd)) { return false; } // If the overlapped-ness isn't the same, we don't subsume. ScanSegmentTree::MergeSegments // will mark that the low-to-high direction needs a VisibilityVertex to link the two segments. // These may differ by more than Curve.DistanceEpsilon in the case of reflection lookahead // segments collinear with vertex-derived segments, so have a looser tolerance here and we'll // adjust the segments in ScanSegmentTree.MergeSegments. if (seg.Weight != weight) { if ((seg.Start == newStart) && (seg.End == newEnd)) { // This is probably because of a rounding difference by one DistanceEpsilon reporting being // inside an obstacle vs. the scanline intersection calculation side-ordering. // Test is RectilinearFileTests.Overlap_Rounding_Vertex_Intersects_Side. seg.Weight = Math.Min(seg.Weight, weight); return true; } // In the case of groups, we go through the group boundary; this may coincide with a // reflection segment. RectilinearFileTests.ReflectionSubsumedBySegmentExitingGroup. Debug.Assert((seg.Weight == OverlappedWeight) == (weight == OverlappedWeight) || ApproximateComparer.CloseIntersections(seg.End, newStart) || ApproximateComparer.CloseIntersections(seg.Start, newEnd) , "non-equal overlap-mismatched ScanSegments overlap by more than just Start/End"); return false; } // Subsume the input segment. Return whether the start/end points were extended (newStart // is before this.Start, or newEnd is after this.End), so the caller can generate reflections // and so we can merge group border crossings. extendStart = (-1 == scanDir.CompareScanCoord(newStart, seg.Start)); extendEnd = (1 == scanDir.CompareScanCoord(newEnd, seg.End)); if (extendStart || extendEnd) { // We order by start and end so need to replace this in the tree regardless of which end changes. tree.Remove(seg); seg.startPoint = scanDir.Min(seg.Start, newStart); seg.endPoint = scanDir.Max(seg.End, newEnd); seg = tree.InsertUnique(seg).Item; seg.MergeGroupBoundaryCrossingList(gbcList); } return true; }