Exemplo n.º 1
0
        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);
        }
Exemplo n.º 2
0
        /// <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);
        }
Exemplo n.º 3
0
 void OnSegmentClose(ScanSegment seg)
 {
     seg.OnSegmentIntersectorEnd(visGraph);
     if (null == seg.LowestVisibilityVertex)
     {
         segmentsWithoutVisibility.Add(seg);
     }
 }
Exemplo n.º 4
0
        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);
        }
Exemplo n.º 5
0
 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);
 }
Exemplo n.º 7
0
 void OnSegmentOpen(ScanSegment seg)
 {
     seg.OnSegmentIntersectorBegin(visGraph);
 }
Exemplo n.º 8
0
        // 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;
 }
Exemplo n.º 11
0
 internal override void Clear()
 {
     base.Clear();
     hintScanSegment = null;
 }
Exemplo n.º 12
0
 internal override void InitializeEventQueue(ScanDirection scanDir)
 {
     base.InitializeEventQueue(scanDir);
     hintScanSegment = null;
 }
Exemplo n.º 13
0
        // 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);
 }
Exemplo n.º 16
0
 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;
        }
Exemplo n.º 18
0
 /// <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;
 }
Exemplo n.º 20
0
 internal bool IntersectsSegment(ScanSegment seg)
 {
     return(StaticGraphUtility.SegmentsIntersect(this, 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;
 }
Exemplo n.º 22
0
 void ScanRemove(ScanSegment seg)
 {
     verticalSegmentsScanLine.Remove(seg);
 }
 /// <summary>
 /// Restores state between intersection passes.
 /// </summary>
 internal void ResetForIntersections() {
     Debug.Assert(null != this.FirstSegment, "Empty ScanSegmentVectorItem");
     this.CurrentSegment = this.FirstSegment;
 }
Exemplo n.º 24
0
 bool IsVSegInHSegRange(ScanSegment v)
 {
     return(PointComparer.Compare(v.Start.X, findFirstHSeg.Start.X) >= 0);
 }
Exemplo n.º 25
0
 internal Point GetIntersection(ScanSegment seg)
 {
     return(StaticGraphUtility.SegmentIntersection(this, seg));
 }
Exemplo n.º 26
0
 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;
        }