internal ScanSegmentTree(ScanDirection scanDir) { ScanDirection = scanDir; this.segmentTree = new RbTree <ScanSegment>(this); this.findIntersectorPred = new Func <ScanSegment, bool>(this.CompareIntersector); this.findPointPred = new Func <ScanSegment, bool>(this.CompareToPoint); }
protected VisibilityGraphGenerator(bool wantReflections) { this.ScanDirection = ScanDirection.HorizontalInstance; this.eventQueue = new EventQueue(); this.HorizontalScanSegments = new ScanSegmentTree(ScanDirection.HorizontalInstance); this.VerticalScanSegments = new ScanSegmentTree(ScanDirection.VerticalInstance); this.wantReflections = wantReflections; }
internal ScanSegment FindSegmentOverlappingPoints(Point start, Point end, bool allowUnfound) { this.lookupSegment.Update(start, end); RBNode <ScanSegment> node = this.segmentTree.FindFirst(this.findPointPred); // If we had any segments in the tree that end after 'start', node has the first one. // Now we need to that it starts before 'end'. ScanSegment.CompareToPointPositionFullLength // asserts the point is on the segment which we don't want to require here, so // compare the endpoints directly. if (null != node) { ScanSegment seg = node.Item; if (ScanDirection.Compare(seg.Start, end) <= 0) { return(seg); } } // Not found. if (!allowUnfound) { Debug.Assert(false, "Could not find expected segment"); } return(null); }
// Find the highest perpendicular scanseg that intersects the segment endpoints. internal ScanSegment FindHighestIntersector(Point start, Point end) { Debug.Assert(ScanDirection.IsPerpendicular(start, end), "non-perpendicular segment passed"); // Find the last segment that starts at or before 'end'. this.lookupSegment.Update(end, end); RBNode <ScanSegment> node = this.segmentTree.FindLast(this.findIntersectorPred); // Now we either have a segment that intersects start/end, or one that ends before // 'end' and need to iterate to find the highest bisector. if (PointComparer.Equal(start, end)) { if ((null != node) && (ScanDirection.Compare(node.Item.End, start) < 0)) { node = null; } } else { this.lookupSegment.Update(start, end); while ((null != node) && !node.Item.IntersectsSegment(this.lookupSegment)) { // If the node segment ends before 'start', no intersection was found. if (ScanDirection.Compare(node.Item.End, start) < 0) { return(null); } node = this.segmentTree.Previous(node); } } return((null != node) ? node.Item : null); }
internal static Obstacle CreateSentinel(Point a, Point b, ScanDirection scanDir, int scanlineOrdinal) { var sentinel = new Obstacle(a, b, scanlineOrdinal); sentinel.CreateInitialSides(sentinel.PaddedPolyline.StartPoint, scanDir); return(sentinel); }
bool CompareToPoint(ScanSegment treeSeg) { // Test if treeSeg overlaps the LookupSegment.Start point. We're using FindFirst, // so we'll just return false for everything that ends before the point and true for anything // that ends at or after it, then the caller will verify overlap. return(ScanDirection.Compare(treeSeg.End, this.lookupSegment.Start) >= 0); }
static internal double Slope(Point start, Point end, ScanDirection scanDir) { // Find the slope relative to scanline - how much scan coord changes per sweep change. Point lineDirAsPoint = end - start; return((lineDirAsPoint * scanDir.PerpDirectionAsPoint) / (lineDirAsPoint * scanDir.DirectionAsPoint)); }
internal RBNode <ScanSegment> FindLowestIntersectorNode(Point start, Point end) { Debug.Assert(ScanDirection.IsPerpendicular(start, end), "non-perpendicular segment passed"); // Find the last segment that starts at or before 'start'. this.lookupSegment.Update(start, start); RBNode <ScanSegment> node = this.segmentTree.FindLast(this.findIntersectorPred); // We have a segment that intersects start/end, or one that ends before 'start' and thus we // must iterate to find the lowest bisector. TODOperf: see how much that iteration costs us // (here and Highest); consider a BSP tree or interval tree (maybe 2-d RBTree for updatability). if (PointComparer.Equal(start, end)) { if ((null != node) && (ScanDirection.Compare(node.Item.End, start) < 0)) { node = null; } } else { this.lookupSegment.Update(start, end); while ((null != node) && !node.Item.IntersectsSegment(this.lookupSegment)) { // If the node segment starts after 'end', no intersection was found. if (ScanDirection.Compare(node.Item.Start, end) > 0) { return(null); } node = this.segmentTree.Next(node); } } return(node); }
/// <summary> /// For ordering the line segments inserted by the ScanLine. Assuming vertical sweep (sweeping up from /// bottom, scanning horizontally) then order ScanSegments first by lowest Y coord, then by lowest X coord. /// </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); } // This orders on both axes. int cmp = ScanDirection.Compare(first.Start, second.Start); if (0 == cmp) { // Longer segments come first, to make overlap removal easier. cmp = -ScanDirection.Compare(first.End, second.End); } return(cmp); }
// Hit-testing. internal bool IntersectionIsInsideAnotherObstacle(Obstacle sideObstacle, Obstacle eventObstacle, Point intersect, ScanDirection scanDirection) { insideHitTestIgnoreObstacle1 = eventObstacle; insideHitTestIgnoreObstacle2 = sideObstacle; insideHitTestScanDirection = scanDirection; RectangleNode <Obstacle, Point> obstacleNode = Root.FirstHitNode(intersect, InsideObstacleHitTest); return(null != obstacleNode); }
internal bool PointIsInsideAnObstacle(Point intersect, ScanDirection scanDirection) { insideHitTestIgnoreObstacle1 = null; insideHitTestIgnoreObstacle2 = null; insideHitTestScanDirection = scanDirection; RectangleNode <Obstacle, Point> obstacleNode = Root.FirstHitNode(intersect, InsideObstacleHitTest); return(null != obstacleNode); }
internal BasicObstacleSide(Obstacle obstacle, PolylinePoint startVertex, ScanDirection scanDir, bool traverseClockwise) : base(startVertex) { Obstacle = obstacle; endVertex = traverseClockwise ? startVertex.NextOnPolyline : startVertex.PrevOnPolyline; if (!scanDir.IsPerpendicular(startVertex.Point, endVertex.Point)) { Slope = StaticGraphUtility.Slope(startVertex.Point, endVertex.Point, scanDir); SlopeInverse = 1.0 / Slope; } }
// Set the initial ActiveLowSide and ActiveHighSide of the obstacle starting at this point. internal void CreateInitialSides(PolylinePoint startPoint, ScanDirection scanDir) { Debug.Assert((null == ActiveLowSide) && (null == ActiveHighSide) , "Cannot call SetInitialSides when sides are already set"); ActiveLowSide = new LowObstacleSide(this, startPoint, scanDir); ActiveHighSide = new HighObstacleSide(this, startPoint, scanDir); if (scanDir.IsFlat(ActiveHighSide)) { // No flat sides in the scanline; we'll do lookahead processing in the scanline to handle overlaps // with existing segments, and normal neighbor handling will take care of collinear OpenVertexEvents. ActiveHighSide = new HighObstacleSide(this, ActiveHighSide.EndVertex, scanDir); } }
internal ScanSegment Find(Point start, Point end) { Debug.Assert(PointComparer.Equal(start, end) || !ScanDirection.IsPerpendicular(start, end) , "perpendicular segment passed"); this.lookupSegment.Update(start, end); RBNode <ScanSegment> node = this.segmentTree.Find(this.lookupSegment); if ((null != node) && PointComparer.Equal(node.Item.End, end)) { return(node.Item); } return(null); }
RBNode <ScanSegment> MergeAndRemoveNextNode(ScanSegment currentSegment, RBNode <ScanSegment> nextSegNode) { // Merge at the ends only - if we're here, start will be the same or greater. if (-1 == ScanDirection.Compare(currentSegment.End, nextSegNode.Item.End)) { currentSegment.Update(currentSegment.Start, nextSegNode.Item.End); } // Removing the node can revise the tree's RBNodes internally so re-get the current segment. currentSegment.MergeGroupBoundaryCrossingList(nextSegNode.Item.GroupBoundaryPointAndCrossingsList); this.segmentTree.DeleteNodeInternal(nextSegNode); return(this.segmentTree.Find(currentSegment)); }
private void SetVectorsAndCoordMaps(ScanDirection scanDir) { if (scanDir.IsHorizontal) { this.parallelSegmentVector = this.horizontalScanSegmentVector; this.perpendicularSegmentVector = this.verticalScanSegmentVector; this.perpendicularCoordMap = this.verticalCoordMap; } else { this.parallelSegmentVector = this.verticalScanSegmentVector; this.perpendicularSegmentVector = this.horizontalScanSegmentVector; this.perpendicularCoordMap = this.horizontalCoordMap; } }
private void AddAxisCoordinateEvents(ScanDirection scanDir) { // Normal event ordering will apply - and will thus order the ScanSegments created in the vectors. if (scanDir.IsHorizontal) { foreach (var coord in yCoordAccumulator) { base.eventQueue.Enqueue(new AxisCoordinateEvent(new Point(ObstacleTree.GraphBox.Left - SentinelOffset, coord))); } return; } foreach (var coord in xCoordAccumulator) { base.eventQueue.Enqueue(new AxisCoordinateEvent(new Point(coord, ObstacleTree.GraphBox.Bottom - SentinelOffset))); } }
internal HighObstacleSide(Obstacle obstacle, PolylinePoint startVertex, ScanDirection scanDir) : base(obstacle, startVertex, scanDir, scanDir.IsVertical /*traverseClockwise*/) { }
internal bool PointIsInsideAnObstacle(Point intersect, ScanDirection scanDirection) { insideHitTestIgnoreObstacle1 = null; insideHitTestIgnoreObstacle2 = null; insideHitTestScanDirection = scanDirection; RectangleNode<Obstacle> obstacleNode = Root.FirstHitNode(intersect, InsideObstacleHitTest); return (null != obstacleNode); }
static internal double Slope(SegmentBase seg, ScanDirection scanDir) { return(Slope(seg.Start, seg.End, scanDir)); }
// 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 virtual void InitializeEventQueue(ScanDirection scanDir) { ScanDirection = scanDir; eventQueue.Reset(ScanDirection); EnqueueBottomVertexEvents(); scanLine = new RectilinearScanLine(ScanDirection, ObstacleTree.GraphBox.LeftBottom); lookaheadScan = new LookaheadScan(ScanDirection); }
internal override void InitializeEventQueue(ScanDirection scanDir) { base.InitializeEventQueue(scanDir); hintScanSegment = null; }
internal void Reset(ScanDirection scanDir) { Debug.Assert(0 == eventTree.Count, "Stray events in EventQueue.Reset"); scanDirection = scanDir; }
bool CompareIntersector(ScanSegment seg) { // We're looking for the last segment that starts before LookupSegment.Start. return(ScanDirection.Compare(seg.Start, this.lookupSegment.Start) <= 0); }
static internal double Slope(SegmentBase seg, ScanDirection scanDir) { return Slope(seg.Start, seg.End, scanDir); }
internal LookaheadScan(ScanDirection scanDir) { scanDirection = scanDir; eventTree = new RbTree <BasicReflectionEvent>(this); findFirstPred = new Func <BasicReflectionEvent, bool>(n => CompareToFindFirstPoint(n.Site) >= 0); }
internal ObstaclePortEntrance(ObstaclePort oport, Point unpaddedBorderIntersect, Direction outDir, ObstacleTree obstacleTree) { ObstaclePort = oport; UnpaddedBorderIntersect = unpaddedBorderIntersect; OutwardDirection = outDir; // Get the padded intersection. var lineSeg = new LineSegment(UnpaddedBorderIntersect, StaticGraphUtility.RectangleBorderIntersect( oport.Obstacle.VisibilityBoundingBox, UnpaddedBorderIntersect, outDir)); IList <IntersectionInfo> xxs = Curve.GetAllIntersections(lineSeg, oport.Obstacle.VisibilityPolyline, true /*liftIntersections*/); Debug.Assert(1 == xxs.Count, "Expected one intersection"); this.VisibilityBorderIntersect = ApproximateComparer.Round(SpliceUtility.RawIntersection(xxs[0], UnpaddedBorderIntersect)); this.MaxVisibilitySegment = obstacleTree.CreateMaxVisibilitySegment(this.VisibilityBorderIntersect, this.OutwardDirection, out this.pointAndCrossingsList); // Groups are never in a clump (overlapped) but they may still have their port entrance overlapped. if (this.Obstacle.IsOverlapped || (this.Obstacle.IsGroup && !this.Obstacle.IsInConvexHull)) { this.IsOverlapped = obstacleTree.IntersectionIsInsideAnotherObstacle(/*sideObstacle:*/ null, this.Obstacle , this.VisibilityBorderIntersect, ScanDirection.GetInstance(OutwardDirection)); if (!this.Obstacle.IsGroup || this.IsOverlapped || this.InteriorEdgeCrossesObstacle(obstacleTree)) { unpaddedToPaddedBorderWeight = ScanSegment.OverlappedWeight; } } if (this.Obstacle.IsInConvexHull && (unpaddedToPaddedBorderWeight == ScanSegment.NormalWeight)) { SetUnpaddedToPaddedBorderWeightFromHullSiblingOverlaps(obstacleTree); } }
// Hit-testing. internal bool IntersectionIsInsideAnotherObstacle(Obstacle sideObstacle, Obstacle eventObstacle, Point intersect, ScanDirection scanDirection) { insideHitTestIgnoreObstacle1 = eventObstacle; insideHitTestIgnoreObstacle2 = sideObstacle; insideHitTestScanDirection = scanDirection; RectangleNode<Obstacle> obstacleNode = Root.FirstHitNode(intersect, InsideObstacleHitTest); return (null != obstacleNode); }
internal RectilinearScanLine(ScanDirection scanDir, Point start) { scanDirection = scanDir; SideTree = new RbTree <BasicObstacleSide>(this); this.linePositionAtLastInsertOrRemove = start; }
void AssertValidSegmentForInsertion(ScanSegment seg) { Debug.Assert((seg.End.X >= seg.Start.X) && (seg.End.Y >= seg.Start.Y), "Reversed direction in ScanSegment"); Debug.Assert(ScanDirection.IsFlat(seg.Start, seg.End), "non-flat segment cannot be inserted"); }
internal LowObstacleSide(Obstacle obstacle, PolylinePoint startVertex, ScanDirection scanDir) : base(obstacle, startVertex, scanDir, scanDir.IsHorizontal /*traverseClockwise*/) { }
internal bool PointIsInsideAnObstacle(Point intersect, Directions direction) { return(PointIsInsideAnObstacle(intersect, ScanDirection.GetInstance(direction))); }
internal override void InitializeEventQueue(ScanDirection scanDir) { base.InitializeEventQueue(scanDir); this.SetVectorsAndCoordMaps(scanDir); this.AddAxisCoordinateEvents(scanDir); }
internal void MergeSegments() { // As described in the doc, hintScanSegment handles all the non-overlapped non-reflection cases. DevTraceInfo(1, "{0} ScanSegmentTree MergeSegments, count = {1}" , ScanDirection.IsHorizontal ? "Horizontal" : "Vertical", this.segmentTree.Count); if (this.segmentTree.Count < 2) { return; } RBNode <ScanSegment> currentSegNode = this.segmentTree.TreeMinimum(); RBNode <ScanSegment> nextSegNode = this.segmentTree.Next(currentSegNode); for ( ; null != nextSegNode; nextSegNode = this.segmentTree.Next(currentSegNode)) { DevTraceInfo(2, "Current {0} Next {1}", currentSegNode.Item, nextSegNode.Item); int cmp = ScanDirection.Compare(nextSegNode.Item.Start, currentSegNode.Item.End); switch (cmp) { case 1: // Next segment starts after the current one. currentSegNode = nextSegNode; break; case 0: if (nextSegNode.Item.IsOverlapped == currentSegNode.Item.IsOverlapped) { // Overlapping is the same, so merge. Because the ordering in the tree is that // same-Start nodes are ordered by longest-End first, this will retain the tree ordering. DevTraceInfo(2, " (merged; start-at-end with same overlap)"); currentSegNode = MergeAndRemoveNextNode(currentSegNode.Item, nextSegNode); } else { // Touching start/end with differing IsOverlapped so they need a connecting vertex. DevTraceInfo(2, " (marked with NeedFinalOverlapVertex)"); currentSegNode.Item.NeedEndOverlapVertex = true; nextSegNode.Item.NeedStartOverlapVertex = true; currentSegNode = nextSegNode; } break; default: // -1 == cmp // nextSegNode.Item.Start is before currentSegNode.Item.End. Debug.Assert((nextSegNode.Item.Start != currentSegNode.Item.Start) || (nextSegNode.Item.End < currentSegNode.Item.End) , "Identical segments are not allowed, and longer ones must come first"); // Because longer segments are ordered before shorter ones at the same start position, // nextSegNode.Item must be a duplicate segment or is partially or totally overlapped. // In the case of reflection lookahead segments, the side-intersection calculated from // horizontal vs. vertical directions may be slightly different along the parallel // coordinate from an overlapped segment, so let non-overlapped win that disagreement. if (currentSegNode.Item.IsOverlapped != nextSegNode.Item.IsOverlapped) { Debug.Assert(ApproximateComparer.CloseIntersections(currentSegNode.Item.End, nextSegNode.Item.Start) , "Segments share a span with different IsOverlapped"); if (currentSegNode.Item.IsOverlapped) { // If the Starts are different, then currentSegNode is the only item at its // start, so we don't need to re-insert. Otherwise, we need to remove it and // re-find nextSegNode's side. if (currentSegNode.Item.Start == nextSegNode.Item.Start) { // currentSegNode is a tiny overlapped segment between two non-overlapped segments (so // we'll have another merge later, when we hit the other non-overlapped segment). // Notice reversed params. TestNote: No longer have repro with the change to convex hulls; // this may no longer happen since overlapped edges will now always be inside rectangular // obstacles so there are no angled-side calculations. currentSegNode = MergeAndRemoveNextNode(nextSegNode.Item, currentSegNode); DevTraceInfo(2, " (identical starts so discarding isOverlapped currentSegNode)"); } else { currentSegNode.Item.Update(currentSegNode.Item.Start, nextSegNode.Item.Start); DevTraceInfo(2, " (trimming isOverlapped currentSegNode to {0})", currentSegNode.Item); currentSegNode = nextSegNode; } } else { if (currentSegNode.Item.End == nextSegNode.Item.End) { // nextSegNode is a tiny non-overlapped segment between two overlapped segments (so // we'll have another merge later, when we hit the other non-overlapped segment). // TestNote: No longer have repro with the change to convex hulls; // this may no longer happen since overlapped edges will now always be inside rectangular // obstacles so there are no angled-side calculations. currentSegNode = MergeAndRemoveNextNode(currentSegNode.Item, nextSegNode); DevTraceInfo(2, " (identical ends so discarding isOverlapped currentSegNode)"); } else { // Remove nextSegNode, increment its start to be after currentSegment, re-insert nextSegNode, and // re-find currentSegNode (there may be more segments between nextSegment.Start and currentSegment.End). ScanSegment nextSegment = nextSegNode.Item; ScanSegment currentSegment = currentSegNode.Item; this.segmentTree.DeleteNodeInternal(nextSegNode); nextSegment.Update(currentSegment.End, nextSegment.End); this.segmentTree.Insert(nextSegment); nextSegment.TrimGroupBoundaryCrossingList(); DevTraceInfo(2, " (trimming isOverlapped nextSegNode to {0})", nextSegment); currentSegNode = this.segmentTree.Find(currentSegment); } } break; } // Overlaps match so do a normal merge operation. DevTraceInfo(2, " (merged; start-before-end)"); currentSegNode = MergeAndRemoveNextNode(currentSegNode.Item, nextSegNode); break; } // endswitch } // endfor #if DEVTRACE DevTraceInfo(1, "{0} ScanSegmentTree after MergeSegments, count = {1}" , ScanDirection.IsHorizontal ? "Horizontal" : "Vertical", this.segmentTree.Count); if (this.scanSegmentVerify.IsLevel(1)) { DevTraceInfo(1, "{0} ScanSegmentTree Consistency Check" , ScanDirection.IsHorizontal ? "Horizontal" : "Vertical"); bool retval = true; RBNode <ScanSegment> prevSegNode = this.segmentTree.TreeMinimum(); currentSegNode = this.segmentTree.Next(prevSegNode); while (null != currentSegNode) { DevTraceInfo(4, currentSegNode.Item.ToString()); // We should only have end-to-end touching, and that only if differing IsOverlapped. if ((-1 != Compare(prevSegNode.Item, currentSegNode.Item)) || (1 != ScanDirection.Compare(currentSegNode.Item.Start, prevSegNode.Item.Start) || ((0 == ScanDirection.Compare(currentSegNode.Item.Start, prevSegNode.Item.End)) && (currentSegNode.Item.IsOverlapped == prevSegNode.Item.IsOverlapped)))) { this.scanSegmentTrace.WriteError(0, "Segments are not strictly increasing:"); this.scanSegmentTrace.WriteFollowup(0, prevSegNode.Item.ToString()); this.scanSegmentTrace.WriteFollowup(0, currentSegNode.Item.ToString()); retval = false; } prevSegNode = currentSegNode; currentSegNode = this.segmentTree.Next(currentSegNode); } Debug.Assert(retval, "ScanSegments are not strictly increasing"); } #endif // DEVTRACE }
static internal double Slope(Point start, Point end, ScanDirection scanDir) { // Find the slope relative to scanline - how much scan coord changes per sweep change. Point lineDirAsPoint = end - start; return (lineDirAsPoint * scanDir.PerpDirectionAsPoint) / (lineDirAsPoint * scanDir.DirectionAsPoint); }
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; }
internal static Obstacle CreateSentinel(Point a, Point b, ScanDirection scanDir, int scanlineOrdinal) { var sentinel = new Obstacle(a, b, scanlineOrdinal); sentinel.CreateInitialSides(sentinel.PaddedPolyline.StartPoint, scanDir); return sentinel; }
// 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; }