// // ScanSegmentVectorItem.cs // MSAGL base class to create the visibility graph consisting of nlogn ScanSegment intersections for Rectilinear Edge Routing. // // Copyright Microsoft Corporation. using System.Collections.Generic; using System.Diagnostics; using Microsoft.Msagl.Core.Geometry; namespace Microsoft.Msagl.Routing.Rectilinear { internal partial class SparseVisibilityGraphGenerator { /// /// This forms one slot in the scan segment vector. /// internal class ScanSegmentVectorItem { /// /// The head of the linked list. /// internal ScanSegment FirstSegment; /// /// The current segment of the linked list, used when appending or intersecting. /// internal ScanSegment CurrentSegment; /// /// Perpendicular coordinates that are not in a ScanSegment, due to either not having the ScanSegments created /// yet or because it will be faster to do a single pass after accumulating them (e.g. for GroupBoundaryCrossings). /// private List pendingPerpCoords; internal void AddPendingPerpendicularCoord(double coord) { if (pendingPerpCoords == null) { pendingPerpCoords = new List(); } pendingPerpCoords.Add(coord); } /// /// Restores state between intersection passes. /// internal void ResetForIntersections() { Debug.Assert(null != this.FirstSegment, "Empty ScanSegmentVectorItem"); this.CurrentSegment = this.FirstSegment; } /// /// Indicates whether ScanSegments in this item are horizontally or vertically oriented. /// internal bool IsHorizontal { get { return !this.FirstSegment.IsVertical; } } /// /// Returns the constant coordinate of the ScanSegments in this item, i.e. the coordinate /// that intersects the perpendicular axis. /// internal double Coord { get; private set; } /// /// Ctor, taking the parallel (constant) coordinate. /// /// the parallel (constant) coordinate internal ScanSegmentVectorItem(double coord) { this.Coord = coord; } /// /// Move along the linked list until we hit the ScanSegment that contains the point. /// internal bool TraverseToSegmentContainingPoint(Point point) { // This is not a simple Next() because scan segments are extended "through" obstacles // (intermixing overlapped and non-overlapped) and thus a ScanSegment's Start and End // may not be in the vertexPoints collection and the ScanSegment must be skipped. if (this.CurrentSegment.ContainsPoint(point)) { return true; } var pointCoord = this.IsHorizontal ? point.Y : point.X; if (!PointComparer.Equal(this.Coord, pointCoord)) { Debug.Assert(PointComparer.Compare(this.Coord, pointCoord) == -1, "point is before current Coord"); while (this.MoveNext()) { // Skip to the end of the linked list if this point is not on the same coordinate. } return false; } for (;;) { // In the event of mismatched rounding on horizontal versus vertical intersections // with a sloped obstacle side, we may have a point that is just before or just // after the current segment. If the point is in some space that doesn't have a // scansegment, and if we are "close enough" to one end or the other of a scansegment, // then grow the scansegment enough to include the new point. if ((null == this.CurrentSegment.NextSegment) || PointComparer.GetDirections(this.CurrentSegment.End, point) == PointComparer.GetDirections(point, this.CurrentSegment.NextSegment.Start)) { if (ApproximateComparer.CloseIntersections(this.CurrentSegment.End, point)) { this.CurrentSegment.Update(this.CurrentSegment.Start, point); return true; } } if (!this.MoveNext()) { return false; } if (this.CurrentSegment.ContainsPoint(point)) { return true; } // This is likely the reverse of the above; the point rounding mismatched to just before // rather than just after the current segment. if (PointComparer.IsPureLower(point, this.CurrentSegment.Start)) { Debug.Assert(ApproximateComparer.CloseIntersections(this.CurrentSegment.Start, point), "Skipped over the point in the ScanSegment linked list"); this.CurrentSegment.Update(point, this.CurrentSegment.End); return true; } } } internal bool MoveNext() { this.CurrentSegment = this.CurrentSegment.NextSegment; return this.HasCurrent; } internal bool HasCurrent { get { return (null != this.CurrentSegment); } } /// /// Returns true if the point is the end of the current segment and there is an adjoining NextSegment. /// internal bool PointIsCurrentEndAndNextStart(Point point) { return (point == this.CurrentSegment.End) && (null != this.CurrentSegment.NextSegment) && (point == this.CurrentSegment.NextSegment.Start); } /// /// Set Current to the ScanSegment containing the perpendicular coordinate, then add that coordinate to its /// sparse-vector coordinate list. /// /// internal void AddPerpendicularCoord(double perpCoord) { var point = this.IsHorizontal ? new Point(perpCoord, this.Coord) : new Point(this.Coord, perpCoord); this.TraverseToSegmentContainingPoint(point); this.CurrentSegment.AddSparseVertexCoord(perpCoord); } /// /// /// public override string ToString() { if (null == this.FirstSegment) { return "-0- " + this.Coord; } return (this.IsHorizontal ? "(H) Y == " : "(V) X == ") + this.Coord; } 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 void AddPendingPerpendicularCoordsToScanSegments() { if (this.pendingPerpCoords != null) { this.ResetForIntersections(); foreach (var point in this.pendingPerpCoords) { this.AddPerpendicularCoord(point); } } } } } }