Aetheria/Assets/Scripts/NIH/MSAGL/Routing/Rectilinear/ScanSegment.cs
hyperion51 01a956eef6 MSAGL Support
Added new tech tree layout implementation using Microsoft Automatic Graph Layouts
Added some functionality to the database editor tools
2020-04-14 00:42:04 +01:00

438 lines
No EOL
21 KiB
C#

//
// 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);
}
/// <summary>
/// </summary>
/// <returns></returns>
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<double> 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<double>();
}
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
}
}