// // ScanSegment.cs // MSAGL class for visibility scan segments for Rectilinear Edge Routing. // // Copyright Microsoft Corporation. using System; using System.Diagnostics; using System.Linq; using Microsoft.Msagl.Core.DataStructures; using Microsoft.Msagl.Core.Geometry; using Microsoft.Msagl.Routing.Visibility; namespace Microsoft.Msagl.Routing.Rectilinear { internal class ScanSegment : SegmentBase { // This is a single segment added by the ScanLine. internal PointAndCrossingsList GroupBoundaryPointAndCrossingsList; private Point endPoint; private Point startPoint; internal const double NormalWeight = VisibilityEdge.DefaultWeight; internal const double ReflectionWeight = 5; internal const double OverlappedWeight = 500; internal double Weight { get; private set; } // For sparse visibility graph. internal ScanSegment NextSegment { get; set; } internal ScanSegment(Point start, Point end) : this(start, end, NormalWeight, gbcList:null) { } internal ScanSegment(Point start, Point end, double weight, PointAndCrossingsList gbcList) { Update(start, end); Weight = weight; GroupBoundaryPointAndCrossingsList = gbcList; } internal override Point Start { get { return startPoint; } } internal override Point End { get { return endPoint; } } internal bool IsVertical { get { return IsVerticalSegment(Start, End); } } internal ScanDirection ScanDirection { get { return IsVertical ? ScanDirection.VerticalInstance : ScanDirection.HorizontalInstance; } } // For fast intersection calculation and ScanSegment splicing. internal VisibilityVertex LowestVisibilityVertex { get; private set; } internal VisibilityVertex HighestVisibilityVertex { get; private set; } // For overlaps, we will need to create a VisibilityVertex at the junction of overlapped/nonoverlapped // segments, but we don't want to create this for non-overlapped situations. internal bool IsOverlapped { get { return OverlappedWeight == this.Weight; } } internal bool IsReflection { get { return ReflectionWeight == this.Weight; } } internal bool NeedStartOverlapVertex { get; set; } internal bool NeedEndOverlapVertex { get; set; } internal static bool IsVerticalSegment(Point start, Point end) { return start.X == end.X; } // For group boundary crossings. internal void MergeGroupBoundaryCrossingList(PointAndCrossingsList other) { if (null != other) { if (null == GroupBoundaryPointAndCrossingsList) { GroupBoundaryPointAndCrossingsList = new PointAndCrossingsList(); } GroupBoundaryPointAndCrossingsList.MergeFrom(other); } } internal void TrimGroupBoundaryCrossingList() { if (null != GroupBoundaryPointAndCrossingsList) { GroupBoundaryPointAndCrossingsList.Trim(Start, End); } } // ctor internal void Update(Point start, Point end) { Debug.Assert(PointComparer.Equal(start, end) || StaticGraphUtility.IsAscending(PointComparer.GetPureDirection(start, end)) , "non-ascending segment"); startPoint = start; endPoint = end; } private void SetInitialVisibilityVertex(VisibilityVertex newVertex) { LowestVisibilityVertex = newVertex; HighestVisibilityVertex = newVertex; } // Append a vertex before LowestVisibilityVertex or after HighestVisibilityVertex. internal void AppendVisibilityVertex(VisibilityGraph vg, VisibilityVertex newVertex) { Debug.Assert(null != newVertex, "newVertex must not be null"); Debug.Assert((null == LowestVisibilityVertex) == (null == HighestVisibilityVertex), "Mismatched null Lowest/HighestVisibilityVertex"); Debug.Assert(StaticGraphUtility.PointIsOnSegment(this, newVertex.Point), "newVertex is out of segment range"); if (null == HighestVisibilityVertex) { if (!AddGroupCrossingsBeforeHighestVisibilityVertex(vg, newVertex)) { SetInitialVisibilityVertex(newVertex); } } else { // In the event of overlaps where ScanSegments share a Start/End at a border, SegmentIntersector // may be appending the same Vertex twice. If that point is on the border of a group, // then we may have just added the border-crossing edge as well. if (PointComparer.IsPureLower(newVertex.Point, HighestVisibilityVertex.Point)) { Debug.Assert(null != vg.FindEdge(newVertex.Point, HighestVisibilityVertex.Point) , "unexpected low/middle insertion to ScanSegment"); return; } // Add the new edge. This will always be in the ascending direction. if (!AddGroupCrossingsBeforeHighestVisibilityVertex(vg, newVertex)) { AppendHighestVisibilityVertex(newVertex); } } } private VisibilityEdge AddVisibilityEdge( VisibilityVertex source, VisibilityVertex target) { Debug.Assert(source.Point != target.Point, "Self-edges are not allowed"); Debug.Assert(PointComparer.IsPureLower(source.Point, target.Point), "Impure or reversed direction encountered"); // Make sure we aren't adding two edges in the same direction to the same vertex. Debug.Assert(null == StaticGraphUtility.FindNextVertex(source, StaticGraphUtility.EdgeDirection(source, target)) , "Duplicate outEdge from Source vertex"); Debug.Assert(null == StaticGraphUtility.FindNextVertex(target, StaticGraphUtility.EdgeDirection(target, source)) , "Duplicate inEdge to Target vertex"); var edge = new VisibilityEdge(source, target, this.Weight); VisibilityGraph.AddEdge(edge); return edge; } private void AppendHighestVisibilityVertex( VisibilityVertex newVertex) { if (!PointComparer.Equal(HighestVisibilityVertex.Point, newVertex.Point)) { AddVisibilityEdge( HighestVisibilityVertex, newVertex); HighestVisibilityVertex = newVertex; } } private void LoadStartOverlapVertexIfNeeded(VisibilityGraph vg) { // For adjacent segments with different IsOverlapped, we need a vertex that // joins the two so a path may be run. This is paired with the other segment's // LoadEndOverlapVertexIfNeeded. if (NeedStartOverlapVertex) { VisibilityVertex vertex = vg.FindVertex(Start); AppendVisibilityVertex(vg, vertex ?? vg.AddVertex(Start)); } } private void LoadEndOverlapVertexIfNeeded(VisibilityGraph vg) { // See comments in LoadStartOverlapVertexIfNeeded. if (NeedEndOverlapVertex) { VisibilityVertex vertex = vg.FindVertex(End); AppendVisibilityVertex(vg, vertex ?? vg.AddVertex(End)); } } internal void OnSegmentIntersectorBegin(VisibilityGraph vg) { // If we process any group crossings, they'll have created the first point. if (!AppendGroupCrossingsThroughPoint(vg, Start)) { LoadStartOverlapVertexIfNeeded(vg); } } internal void OnSegmentIntersectorEnd(VisibilityGraph vg) { AppendGroupCrossingsThroughPoint(vg, End); GroupBoundaryPointAndCrossingsList = null; if ((null == HighestVisibilityVertex) || (PointComparer.IsPureLower(HighestVisibilityVertex.Point, End))) { LoadEndOverlapVertexIfNeeded(vg); } } // 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 bool IntersectsSegment(ScanSegment seg) { return StaticGraphUtility.SegmentsIntersect(this, seg); } /// /// /// public override string ToString() { return "[" + Start + " -> " + End + (IsOverlapped ? " olap" : " free") + "]"; } #region SparseVisibilityGraphGenerator utilities internal bool ContainsPoint(Point test) { // This may be off the line so do not use GetPureDirections. return PointComparer.Equal(this.Start, test) || PointComparer.Equal(this.End, test) || (PointComparer.GetDirections(this.Start, test) == PointComparer.GetDirections(test, this.End)); } private Set sparsePerpendicularCoords; internal bool HasSparsePerpendicularCoords { get { return (this.sparsePerpendicularCoords == null) ? false : (this.sparsePerpendicularCoords.Count > 0); } } private Point CreatePointFromPerpCoord(double perpCoord) { return this.IsVertical ? new Point(this.Start.X, perpCoord) : new Point(perpCoord, this.Start.Y); } internal void AddSparseVertexCoord(double perpCoord) { Debug.Assert(this.ContainsPoint(this.CreatePointFromPerpCoord(perpCoord)), "vertexLocation is not on Segment"); if (this.sparsePerpendicularCoords == null) { this.sparsePerpendicularCoords = new Set(); } this.sparsePerpendicularCoords.Insert(perpCoord); } internal bool AddSparseEndpoint(double coord) { // This is called after AddSparseVertexCoord so this.sparsePerpendicularCoords is already instantiated. if (!this.sparsePerpendicularCoords.Contains(coord)) { this.sparsePerpendicularCoords.Insert(coord); return true; } return false; } internal void CreateSparseVerticesAndEdges(VisibilityGraph vg) { if (this.sparsePerpendicularCoords == null) { return; } AppendGroupCrossingsThroughPoint(vg, Start); foreach (var perpCoord in this.sparsePerpendicularCoords.OrderBy(d => d)) { var vertexLocation = this.CreatePointFromPerpCoord(perpCoord); Debug.Assert(this.ContainsPoint(vertexLocation), "vertexLocation is not on Segment"); this.AppendVisibilityVertex(vg, vg.FindVertex(vertexLocation) ?? vg.AddVertex(vertexLocation)); } AppendGroupCrossingsThroughPoint(vg, End); GroupBoundaryPointAndCrossingsList = null; this.sparsePerpendicularCoords.Clear(); this.sparsePerpendicularCoords = null; } internal bool HasVisibility() { // Skip this only if it has no visibility vertex. return (null != this.LowestVisibilityVertex); } #endregion // SparseVisibilityGraphGenerator utilities #region Group utilities private bool AddGroupCrossingsBeforeHighestVisibilityVertex(VisibilityGraph vg, VisibilityVertex newVertex) { if (AppendGroupCrossingsThroughPoint(vg, newVertex.Point)) { // We may have added an interior vertex that is just higher than newVertex. if (PointComparer.IsPureLower(HighestVisibilityVertex.Point, newVertex.Point)) { AddVisibilityEdge(HighestVisibilityVertex, newVertex); HighestVisibilityVertex = newVertex; } return true; } return false; } private bool AppendGroupCrossingsThroughPoint(VisibilityGraph vg, Point lastPoint) { if (null == GroupBoundaryPointAndCrossingsList) { return false; } bool found = false; while (GroupBoundaryPointAndCrossingsList.CurrentIsBeforeOrAt(lastPoint)) { // We will only create crossing Edges that the segment actually crosses, not those it ends before crossing. // For those terminal crossings, the adjacent segment creates the interior vertex and crossing edge. PointAndCrossings pac = GroupBoundaryPointAndCrossingsList.Pop(); GroupBoundaryCrossing[] lowDirCrossings = null; GroupBoundaryCrossing[] highDirCrossings = null; if (PointComparer.Compare(pac.Location, Start) > 0) { lowDirCrossings = PointAndCrossingsList.ToCrossingArray(pac.Crossings, ScanDirection.OppositeDirection); } if (PointComparer.Compare(pac.Location, End) < 0) { highDirCrossings = PointAndCrossingsList.ToCrossingArray(pac.Crossings, ScanDirection.Direction); } found = true; VisibilityVertex crossingVertex = vg.FindVertex(pac.Location) ?? vg.AddVertex(pac.Location); if ((null != lowDirCrossings) || (null != highDirCrossings)) { AddLowCrossings(vg, crossingVertex, lowDirCrossings); AddHighCrossings(vg, crossingVertex, highDirCrossings); } else { // This is at this.Start with only lower-direction toward group interior(s), or at this.End with only // higher-direction toward group interior(s). Therefore an adjacent ScanSegment will create the crossing // edge, so create the crossing vertex here and we'll link to it. if (null == LowestVisibilityVertex) { SetInitialVisibilityVertex(crossingVertex); } else { Debug.Assert(PointComparer.Equal(End, crossingVertex.Point), "Expected this.End crossingVertex"); AppendHighestVisibilityVertex(crossingVertex); } } } return found; } private static VisibilityVertex GetCrossingInteriorVertex(VisibilityGraph vg, VisibilityVertex crossingVertex, GroupBoundaryCrossing crossing) { Point interiorPoint = crossing.GetInteriorVertexPoint(crossingVertex.Point); return vg.FindVertex(interiorPoint) ?? vg.AddVertex(interiorPoint); } private void AddCrossingEdge(VisibilityGraph vg, VisibilityVertex lowVertex, VisibilityVertex highVertex, GroupBoundaryCrossing[] crossings) { VisibilityEdge edge = null; if (null != HighestVisibilityVertex) { // We may have a case where point xx.xxxxx8 has added an ascending-direction crossing, and now we're on // xx.xxxxx9 adding a descending-direction crossing. In that case there should already be a VisibilityEdge // in the direction we want. if (PointComparer.Equal(this.HighestVisibilityVertex.Point, highVertex.Point)) { edge = vg.FindEdge(lowVertex.Point, highVertex.Point); Debug.Assert(edge != null, "Inconsistent forward-backward sequencing in HighVisibilityVertex"); } else { AppendHighestVisibilityVertex(lowVertex); } } if (edge == null) { edge = AddVisibilityEdge(lowVertex, highVertex); } var crossingsArray = crossings.Select(c => c.Group.InputShape).ToArray(); var prevIsPassable = edge.IsPassable; if (prevIsPassable == null) { edge.IsPassable = delegate { return crossingsArray.Any(s => s.IsTransparent); }; } else { // Because we don't have access to the previous delegate's internals, we have to chain. Fortunately this // will never be more than two deep. File Test: Groups_Forward_Backward_Between_Same_Vertices. edge.IsPassable = delegate { return crossingsArray.Any(s => s.IsTransparent) || prevIsPassable(); }; } if (null == LowestVisibilityVertex) { SetInitialVisibilityVertex(lowVertex); } HighestVisibilityVertex = highVertex; } private void AddLowCrossings(VisibilityGraph vg, VisibilityVertex crossingVertex, GroupBoundaryCrossing[] crossings) { if (null != crossings) { VisibilityVertex interiorVertex = GetCrossingInteriorVertex(vg, crossingVertex, crossings[0]); this.AddCrossingEdge(vg, interiorVertex, crossingVertex, crossings); // low-to-high } } private void AddHighCrossings(VisibilityGraph vg, VisibilityVertex crossingVertex, GroupBoundaryCrossing[] crossings) { if (null != crossings) { VisibilityVertex interiorVertex = GetCrossingInteriorVertex(vg, crossingVertex, crossings[0]); this.AddCrossingEdge(vg, crossingVertex, interiorVertex, crossings); // low-to-high } } #endregion // Group utilities } }