internal static bool FindBracketingVertices(VisibilityVertex sourceVertex, Point targetPoint, Directions dirToTarget , out VisibilityVertex bracketSource, out VisibilityVertex bracketTarget) { // Walk from the source to target until we bracket target or there is no nextVertex // in the desired direction. bracketSource = sourceVertex; for (; ;) { bracketTarget = StaticGraphUtility.FindNextVertex(bracketSource, dirToTarget); if (null == bracketTarget) { break; } if (PointComparer.Equal(bracketTarget.Point, targetPoint)) { // Desired edge already exists. return(true); } if (dirToTarget != PointComparer.GetPureDirection(bracketTarget.Point, targetPoint)) { // bracketTarget is past vertex in the traversal direction. break; } bracketSource = bracketTarget; } return(null != bracketTarget); }
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))); }
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); }
static internal Point RawIntersection(IntersectionInfo xx, Point origin) { // If this fires, then you didn't pass the LineSegment as the first argument to GetAllIntersections. Debug.Assert(xx.Segment0 is LineSegment, "LineSegment was not first arg to GetAllIntersections"); // The intersection snaps the end of the intersection to the PolylinePoint at the start/end // of the interesecting segment on the obstacle if the intersection is Curve.CloseIntersections // to that segment endpoint, which can return a point that is just more than Curve.DistanceEpsilon // off the line. Therefore, re-create the intersection using the LineSegment and intersection // parameters (this assumes the LineSegment.End is not Curve.CloseIntersections to the intersection). Point point = xx.Segment0[xx.Par0]; #if TEST_MSAGL // This may legitimately be rounding-error'd in the same way as xx.IntersectionPoint (and the // caller addresses this later). The purpose of the assert is to verify that the LineSegment // interception is not outside the bbox in the perpendicular direction. var lineSeg = (LineSegment)xx.Segment0; if (StaticGraphUtility.IsVertical(PointComparer.GetDirections(lineSeg.Start, lineSeg.End))) { Debug.Assert(PointComparer.Equal(point.X, origin.X), "segment0 obstacle intersection is off the vertical line"); } else { Debug.Assert(PointComparer.Equal(point.Y, origin.Y), "segment0 obstacle intersection is off the horizontal line"); } #endif // TEST_MSAGL return(ApproximateComparer.Round(point)); }
/// <summary> /// Move along the linked list until we hit the ScanSegment that contains the point. /// </summary> 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); } } }
private static VisibilityVertex GetSpliceTarget(ref VisibilityVertex spliceSource, Directions spliceTargetDir, Point nextExtendPoint) { // Look for the target. There may be a dead-ended edge starting at the current spliceSource // edge that has a vertex closer to the extension line; in that case keep walking until we // have the closest vertex on the Source side of the extension line as spliceSource. Directions prevDir = PointComparer.GetPureDirection(spliceSource.Point, nextExtendPoint); Directions nextDir = prevDir; var spliceTarget = spliceSource; while (nextDir == prevDir) { spliceSource = spliceTarget; spliceTarget = StaticGraphUtility.FindNextVertex(spliceSource, spliceTargetDir); if (null == spliceTarget) { break; } if (PointComparer.Equal(spliceTarget.Point, nextExtendPoint)) { // If we encountered an existing vertex for the extension chain, update spliceTarget // to be after it and we're done with this loop. spliceTarget = StaticGraphUtility.FindNextVertex(spliceTarget, spliceTargetDir); break; } nextDir = PointComparer.GetPureDirection(spliceTarget.Point, nextExtendPoint); } return(spliceTarget); }
// 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); }
HitTestBehavior InsideObstacleHitTest(Point location, Obstacle obstacle) { if ((obstacle == insideHitTestIgnoreObstacle1) || (obstacle == insideHitTestIgnoreObstacle2)) { // It's one of the two obstacles we already know about. return(HitTestBehavior.Continue); } if (obstacle.IsGroup) { // Groups are handled differently from overlaps; we create ScanSegments (overlapped // if within a non-group obstacle, else non-overlapped), and turn on/off access across // the Group boundary vertices. return(HitTestBehavior.Continue); } if (!StaticGraphUtility.PointIsInRectangleInterior(location, obstacle.VisibilityBoundingBox)) { // The point is on the obstacle boundary, not inside it. return(HitTestBehavior.Continue); } // Note: There are rounding issues using Curve.PointRelativeToCurveLocation at angled // obstacle boundaries, hence this function. Point high = StaticGraphUtility.RectangleBorderIntersect(obstacle.VisibilityBoundingBox, location , insideHitTestScanDirection.Direction) + insideHitTestScanDirection.DirectionAsPoint; Point low = StaticGraphUtility.RectangleBorderIntersect(obstacle.VisibilityBoundingBox, location , insideHitTestScanDirection.OppositeDirection) - insideHitTestScanDirection.DirectionAsPoint; var testSeg = new LineSegment(low, high); IList <IntersectionInfo> xxs = Curve.GetAllIntersections(testSeg, obstacle.VisibilityPolyline, true /*liftIntersections*/); // If this is an extreme point it can have one intersection, in which case we're either on the border // or outside; if it's a collinear flat boundary, there can be 3 intersections to this point which again // means we're on the border (and 3 shouldn't happen anymore with the curve intersection fixes and // PointIsInsideRectangle check above). So the interesting case is that we have 2 intersections. if (2 == xxs.Count) { Point firstInt = SpliceUtility.RawIntersection(xxs[0], location); Point secondInt = SpliceUtility.RawIntersection(xxs[1], location); // If we're on either intersection, we're on the border rather than inside. if (!PointComparer.Equal(location, firstInt) && !PointComparer.Equal(location, secondInt) && (location.CompareTo(firstInt) != location.CompareTo(secondInt))) { // We're inside. However, this may be an almost-flat side, in which case rounding // could have reported the intersection with the start or end of the same side and // a point somewhere on the interior of that side. Therefore if both intersections // are on the same side (integral portion of the parameter), we consider location // to be on the border. testSeg is always xxs[*].Segment0. Debug.Assert(testSeg == xxs[0].Segment0, "incorrect parameter ordering to GetAllIntersections"); if (!ApproximateComparer.Close(Math.Floor(xxs[0].Par1), Math.Floor(xxs[1].Par1))) { return(HitTestBehavior.Stop); } } } return(HitTestBehavior.Continue); }
internal void ConnectVertexToTargetVertex(VisibilityVertex sourceVertex, VisibilityVertex targetVertex, Directions finalEdgeDir, double weight) { // finalDir is the required direction of the final edge to the targetIntersect // (there will be two edges if we have to add a bend vertex). StaticGraphUtility.Assert(PointComparer.IsPureDirection(finalEdgeDir), "finalEdgeDir is not pure", ObstacleTree, VisGraph); // targetIntersect may be CenterVertex if that is on an extreme bend or a flat border. if (PointComparer.Equal(sourceVertex.Point, targetVertex.Point)) { return; } // If the target is collinear with sourceVertex we can just create one edge to it. Directions targetDirs = PointComparer.GetDirections(sourceVertex.Point, targetVertex.Point); if (PointComparer.IsPureDirection(targetDirs)) { FindOrAddEdge(sourceVertex, targetVertex); return; } // Not collinear so we need to create a bend vertex and edge if they don't yet exist. Point bendPoint = StaticGraphUtility.FindBendPointBetween(sourceVertex.Point, targetVertex.Point, finalEdgeDir); VisibilityVertex bendVertex = FindOrAddVertex(bendPoint); FindOrAddEdge(sourceVertex, bendVertex, weight); // Now create the outer target vertex if it doesn't exist. FindOrAddEdge(bendVertex, targetVertex, weight); }
private void AppendHighestVisibilityVertex(VisibilityVertex newVertex) { if (!PointComparer.Equal(HighestVisibilityVertex.Point, newVertex.Point)) { AddVisibilityEdge(HighestVisibilityVertex, newVertex); HighestVisibilityVertex = newVertex; } }
internal bool SegmentCrossesANonGroupObstacle(Point startPoint, Point endPoint) { stopAtGroups = false; wantGroupCrossings = false; LineSegment obstacleIntersectSeg = RestrictSegmentPrivate(startPoint, endPoint); return(!PointComparer.Equal(obstacleIntersectSeg.End, endPoint)); }
// 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; }
internal void AddToAdjacentVertex(TransientGraphUtility transUtil , VisibilityVertex targetVertex, Directions dirToExtend, Rectangle limitRect) { if (!PointComparer.Equal(this.Point, targetVertex.Point)) { transUtil.FindOrAddEdge(this.Vertex, targetVertex, InitialWeight); } ExtendEdgeChain(transUtil, targetVertex, dirToExtend, limitRect); }
static internal bool IntervalsAreCollinear(Point start1, Point end1, Point start2, Point end2) { Debug.Assert(IsVertical(start1, end1) == IsVertical(start2, end2), "segments are not in the same orientation"); bool vertical = IsVertical(start1, end1); if (IsVertical(start2, end2) == vertical) { // This handles touching endpoints as well. return(vertical ? PointComparer.Equal(start1.X, start2.X) : PointComparer.Equal(start1.Y, start2.Y)); } 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); }
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); }
internal VisibilityEdge SplitEdge(VisibilityEdge edge, VisibilityVertex splitVertex) { // If the edge is NULL it means we could not find an appropriate one, so do nothing. if (null == edge) { return(null); } StaticGraphUtility.Assert(StaticGraphUtility.PointIsOnSegment(edge.SourcePoint, edge.TargetPoint, splitVertex.Point) , "splitVertex is not on edge", ObstacleTree, VisGraph); if (PointComparer.Equal(edge.Source.Point, splitVertex.Point) || PointComparer.Equal(edge.Target.Point, splitVertex.Point)) { // No split needed. return(edge); } // Store the original edge, if needed. if (!(edge is TollFreeVisibilityEdge)) { edgesToRestore.Add(edge); } VisibilityGraph.RemoveEdge(edge); // If this is an overlapped edge, or we're in sparseVg, then it may be an unpadded->padded edge that crosses // over another obstacle's padded boundary, and then either a collinear splice from a free point or another // obstacle in the same cluster starts splicing from that leapfrogged boundary, so we have the edges: // A -> D | D is unpadded, A is padded border of sourceObstacle // B -> C -> E -> F | B and C are vertical ScanSegments between A and D // <-- splice direction is West | F is unpadded, E is padded border of targetObstacle // Now after splicing F to E to C to B we go A, calling FindOrAddEdge B->A; the bracketing process finds // A->D which we'll be splitting at B, which would wind up with A->B, B->C, B->D, having to Eastward // outEdges from B. See RectilinearTests.Reflection_Block1_Big_UseRect for overlapped, and // RectilinearTests.FreePortLocationRelativeToTransientVisibilityEdgesSparseVg for sparseVg. // To avoid this we add the edges in each direction from splitVertex with FindOrAddEdge. If we've // come here from a previous call to FindOrAddEdge, then that call has found the bracketing vertices, // which are the endpoints of 'edge', and we've removed 'edge', so we will not call SplitEdge again. if ((this.IsSparseVg || (edge.Weight == ScanSegment.OverlappedWeight)) && (splitVertex.Degree > 0)) { FindOrAddEdge(splitVertex, edge.Source, edge.Weight); return(FindOrAddEdge(splitVertex, edge.Target, edge.Weight)); } // Splice it into the graph in place of targetEdge. Return the first half, because // this may be called from AddEdge, in which case the split vertex is the target vertex. CreateEdge(splitVertex, edge.Target, edge.Weight); return(CreateEdge(edge.Source, splitVertex, edge.Weight)); }
/// <summary> /// Add an EdgeGeometry to route /// </summary> /// <param name="edgeGeometry"></param> public void AddEdgeGeometryToRoute(EdgeGeometry edgeGeometry) { ValidateArg.IsNotNull(edgeGeometry, "edgeGeometry"); // The Port.Location values are not necessarily rounded by the caller. The values // will be rounded upon acquisition in PortManager.cs. PointComparer.Equal expects // all values to be rounded. if (!PointComparer.Equal(ApproximateComparer.Round(edgeGeometry.SourcePort.Location) , ApproximateComparer.Round(edgeGeometry.TargetPort.Location))) { EdgeGeometries.Add(edgeGeometry); } else { selfEdges.Add(edgeGeometry); } }
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; }
internal void ExtendEdgeChain(VisibilityVertex startVertex, Rectangle limitRect, LineSegment maxVisibilitySegment, PointAndCrossingsList pacList, bool isOverlapped) { var dir = PointComparer.GetDirections(maxVisibilitySegment.Start, maxVisibilitySegment.End); if (dir == Directions.None) { return; } Debug.Assert(CompassVector.IsPureDirection(dir), "impure max visibility segment"); // Shoot the edge chain out to the shorter of max visibility or intersection with the limitrect. StaticGraphUtility.Assert(PointComparer.Equal(maxVisibilitySegment.Start, startVertex.Point) || (PointComparer.GetPureDirection(maxVisibilitySegment.Start, startVertex.Point) == dir) , "Inconsistent direction found", ObstacleTree, VisGraph); double oppositeFarBound = StaticGraphUtility.GetRectangleBound(limitRect, dir); Point maxDesiredSplicePoint = StaticGraphUtility.IsVertical(dir) ? ApproximateComparer.Round(new Point(startVertex.Point.X, oppositeFarBound)) : ApproximateComparer.Round(new Point(oppositeFarBound, startVertex.Point.Y)); if (PointComparer.Equal(maxDesiredSplicePoint, startVertex.Point)) { // Nothing to do. return; } if (PointComparer.GetPureDirection(startVertex.Point, maxDesiredSplicePoint) != dir) { // It's in the opposite direction, so no need to do anything. return; } // If maxDesiredSplicePoint is shorter, create a new shorter segment. We have to pass both segments // through to the worker function so it knows whether it can go past maxDesiredSegment (which may be limited // by limitRect). var maxDesiredSegment = maxVisibilitySegment; if (PointComparer.GetDirections(maxDesiredSplicePoint, maxDesiredSegment.End) == dir) { maxDesiredSegment = new LineSegment(maxDesiredSegment.Start, maxDesiredSplicePoint); } ExtendEdgeChain(startVertex, dir, maxDesiredSegment, maxVisibilitySegment, pacList, isOverlapped); }
internal VisibilityVertex AddEdgeToTargetEdge(VisibilityVertex sourceVertex, VisibilityEdge targetEdge , Point targetIntersect) { StaticGraphUtility.Assert(PointComparer.Equal(sourceVertex.Point, targetIntersect) || PointComparer.IsPureDirection(sourceVertex.Point, targetIntersect) , "non-orthogonal edge request", ObstacleTree, VisGraph); StaticGraphUtility.Assert(StaticGraphUtility.PointIsOnSegment(targetEdge.SourcePoint, targetEdge.TargetPoint, targetIntersect) , "targetIntersect is not on targetEdge", ObstacleTree, VisGraph); // If the target vertex does not exist, we must split targetEdge to add it. VisibilityVertex targetVertex = VisGraph.FindVertex(targetIntersect); if (null == targetVertex) { targetVertex = AddVertex(targetIntersect); SplitEdge(targetEdge, targetVertex); } FindOrAddEdge(sourceVertex, targetVertex); return(targetVertex); }
internal void ExtendEdgeChain(TransientGraphUtility transUtil, VisibilityVertex targetVertex, Directions dirToExtend, Rectangle limitRect) { // Extend the edge chain to the opposite side of the limit rectangle. StaticGraphUtility.Assert(PointComparer.Equal(this.Point, targetVertex.Point) || (PointComparer.GetPureDirection(this.Point, targetVertex.Point) == dirToExtend) , "input dir does not match with to-targetVertex direction", transUtil.ObstacleTree, transUtil.VisGraph); var extendOverlapped = IsOverlapped; if (extendOverlapped) { // The initial vertex we connected to may be on the border of the enclosing obstacle, // or of another also-overlapped obstacle. If the former, we turn off overlap now. extendOverlapped = transUtil.ObstacleTree.PointIsInsideAnObstacle(targetVertex.Point, dirToExtend); } // If we're inside an obstacle's boundaries we'll never extend past the end of the obstacle // due to encountering the boundary from the inside. So start the extension at targetVertex. SegmentAndCrossings segmentAndCrossings = GetSegmentAndCrossings(this.IsOverlapped ? targetVertex : this.Vertex, dirToExtend, transUtil); transUtil.ExtendEdgeChain(targetVertex, limitRect, segmentAndCrossings.Item1, segmentAndCrossings.Item2, extendOverlapped); }
// 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); }
static internal bool PointIsOnSegment(Point first, Point second, Point test) { return(PointComparer.Equal(first, test) || PointComparer.Equal(second, test) || (PointComparer.GetPureDirection(first, test) == PointComparer.GetPureDirection(test, second))); }
static internal bool IntervalsAreSame(Point start1, Point end1, Point start2, Point end2) { return(PointComparer.Equal(start1, start2) && PointComparer.Equal(end1, end2)); }
/// <summary> /// Route a single stage of a possibly multi-stage (due to waypoints) path. /// </summary> /// <param name="sourceVertexEntries">The VertexEntry array that was in the source vertex if it was the target of a prior stage.</param> /// <param name="sources">The enumeration of source vertices; must be only one if sourceVertexEntries is non-null.</param> /// <param name="targets">The enumeration of target vertex entries; must be only one if targetVertexEntries is non-null.</param> /// <param name="targetVertexEntries">The VertexEntry array that is in the target at the end of the stage.</param> private VertexEntry GetPathStage(VertexEntry[] sourceVertexEntries, IEnumerable <VisibilityVertex> sources, VertexEntry[] targetVertexEntries, IEnumerable <VisibilityVertex> targets) { var ssstCalculator = new SsstRectilinearPath(); VertexEntry bestEntry = null; // This contains the best (lowest) path cost after normalizing origins to the center of the sources // and targets. This is used to avoid selecting a vertex pair whose path has more bends than another pair of // vertices, but the bend penalty didn't total enough to offset the additional length between the "better" pair. // This also plays the role of an upper bound on the path length; if a path cost is greater than adjustedMinCost // then we stop exploring it, which saves considerable time after low-cost paths have been found. double bestCost = double.MaxValue / ScanSegment.OverlappedWeight; double bestPathCostRatio = double.PositiveInfinity; // Calculate the bend penalty multiplier. This is a percentage of the distance between the source and target, // so that we have the same relative importance if we have objects of about size 20 that are about 100 apart // as for objects of about size 200 that are about 1000 apart. Point sourceCenter = Barycenter(sources); Point targetCenter = Barycenter(targets); var distance = SsstRectilinearPath.ManhattanDistance(sourceCenter, targetCenter); ssstCalculator.BendsImportance = Math.Max(0.001, distance * (this.bendPenaltyAsAPercentageOfDistance * 0.01)); // We'll normalize by adding (a proportion of) the distance (only; not bends) from the current endpoints to // their centers. This is similar to routeToCenter, but routing multiple paths like this means we'll always // get at least a tie for the best vertex pair, whereas routeToCenter can introduce extraneous bends // if the sources/targets are not collinear with the center (such as an E-R diagram). // interiorLengthAdjustment is a way to decrease the cost adjustment slightly to allow a bend if it saves moving // a certain proportion of the distance parallel to the object before turning to it. var interiorLengthAdjustment = ssstCalculator.LengthImportance; // VertexEntries for the current pass of the current stage, if multistage. var tempTargetEntries = (targetVertexEntries != null) ? this.currentPassTargetEntries : null; // Process closest pairs first, so we can skip longer ones (jump out of SsstRectilinear sooner, often immediately). // This means that we'll be consistent on tiebreaking for equal scores with differing bend counts (the shorter // path will win). In overlapped graphs the shortest path may have more higher-weight edges. foreach (var pair in from VisibilityVertexRectilinear source in sources from VisibilityVertexRectilinear target in targets orderby SsstRectilinearPath.ManhattanDistance(source.Point, target.Point) select new { sourceV = source, targetV = target }) { var source = pair.sourceV; var target = pair.targetV; if (PointComparer.Equal(source.Point, target.Point)) { continue; } var sourceCostAdjustment = SsstRectilinearPath.ManhattanDistance(source.Point, sourceCenter) * interiorLengthAdjustment; var targetCostAdjustment = SsstRectilinearPath.ManhattanDistance(target.Point, targetCenter) * interiorLengthAdjustment; var adjustedBestCost = bestCost; if (targetVertexEntries != null) { Array.Clear(tempTargetEntries, 0, tempTargetEntries.Length); adjustedBestCost = ssstCalculator.MultistageAdjustedCostBound(bestCost); } VertexEntry lastEntry = ssstCalculator.GetPathWithCost(sourceVertexEntries, source, sourceCostAdjustment, tempTargetEntries, target, targetCostAdjustment, adjustedBestCost); if (tempTargetEntries != null) { UpdateTargetEntriesForEachDirection(targetVertexEntries, tempTargetEntries, ref bestCost, ref bestEntry); continue; } // This is the final (or only) stage. Break ties by picking the lowest ratio of cost to ManhattanDistance between the endpoints. if (lastEntry == null) { continue; } var costRatio = lastEntry.Cost / SsstRectilinearPath.ManhattanDistance(source.Point, target.Point); if ((lastEntry.Cost < bestCost) || ApproximateComparer.Close(lastEntry.Cost, bestCost) && (costRatio < bestPathCostRatio)) { bestCost = lastEntry.Cost; bestEntry = lastEntry; bestPathCostRatio = lastEntry.Cost / SsstRectilinearPath.ManhattanDistance(source.Point, target.Point); } } return(bestEntry); }
static bool DevTrace_IsEndOfEdge(VisibilityEdge e, Point x) { return(PointComparer.Equal(e.SourcePoint, x) || PointComparer.Equal(e.TargetPoint, x)); }
static bool IsSkippableSpliceSourceEdgeWithNullTarget(VisibilityEdge spliceSourceEdge) { return((null != spliceSourceEdge) && (null != spliceSourceEdge.IsPassable) && (PointComparer.Equal(spliceSourceEdge.Length, GroupBoundaryCrossing.BoundaryWidth))); }
internal bool IsPerpendicular(Point start, Point end) { // Return true if there is no change in the primary direction. return(PointComparer.Equal((end - start) * DirectionAsPoint, 0.0)); }