Aetheria/Assets/Plugins/MSAGL/Routing/Rectilinear/ScanSegmentTree.cs
Reinier Vens dbc522a224 Weapos Rejiggered
Modified weapon scripts to eliminate specialized projectile behavior - allows charged weapons to have projectiles and modify weapon stats when charged
Added machine gun weapon & VFX
Moved third party code to Plugins folder to improve compile times
2021-01-31 03:52:32 +00:00

372 lines
21 KiB
C#

//
// ScanSegmentTree.cs
// MSAGL class for visibility scan segment tree for Rectilinear Edge Routing.
//
// Copyright Microsoft Corporation.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Msagl.Core.DataStructures;
using Microsoft.Msagl.Core.Geometry;
namespace Microsoft.Msagl.Routing.Rectilinear {
using DebugHelpers;
internal class ScanSegmentTree : IComparer<ScanSegment> {
internal ScanDirection ScanDirection { get; private set; }
private readonly RbTree<ScanSegment> segmentTree;
// Temporary variables for lookup.
private readonly ScanSegment lookupSegment = new ScanSegment(new Point(0, 0), new Point(0, 1)); // dummy values avoid AF; will be overwritten
readonly Func<ScanSegment, bool> findIntersectorPred;
readonly Func<ScanSegment, bool> findPointPred;
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);
}
internal IEnumerable<ScanSegment> Segments { get { return this.segmentTree; } }
// If the seg is already in the tree it returns that instance, else it inserts the new
// seg and returns that.
internal RBNode<ScanSegment> InsertUnique(ScanSegment seg) {
// RBTree's internal operations on insert/remove etc. mean the node can't cache the
// RBNode returned by insert(); instead we must do find() on each call. But we can
// use the returned node to get predecessor/successor.
AssertValidSegmentForInsertion(seg);
var node = this.segmentTree.Find(seg);
if (null != node) {
Debug.Assert(seg.IsOverlapped == node.Item.IsOverlapped, "Existing node found with different isOverlapped");
return node;
}
return this.segmentTree.Insert(seg);
}
[Conditional("DEBUG")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
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 void Remove(ScanSegment seg) {
Debug.Assert(seg.IsVertical == ScanDirection.IsVertical, "seg.IsVertical != ScanDirection.IsVertical");
this.segmentTree.Remove(seg);
}
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;
}
// Find the lowest perpendicular scanseg that intersects the segment endpoints.
internal ScanSegment FindLowestIntersector(Point start, Point end) {
var node = FindLowestIntersectorNode(start, end);
return (null != node) ? node.Item : null;
}
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;
}
// 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;
}
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);
}
internal ScanSegment FindSegmentContainingPoint(Point location, bool allowUnfound) {
return FindSegmentOverlappingPoints(location, location, allowUnfound);
}
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;
}
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);
}
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);
}
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
}
#region IComparer<ScanSegment>
/// <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;
}
#endregion // IComparer<ScanSegment>
#region DevTrace
#if DEVTRACE
readonly DevTrace scanSegmentTrace = new DevTrace("Rectilinear_ScanSegmentTrace", "ScanSegments");
readonly DevTrace scanSegmentVerify = new DevTrace("Rectilinear_ScanSegmentVerify");
#endif // DEVTRACE
[Conditional("DEVTRACE")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
void DevTraceInfo(int verboseLevel, string format, params object[] args) {
#if DEVTRACE
this.scanSegmentTrace.WriteLineIf(DevTrace.Level.Info, verboseLevel, format, args);
#endif // DEVTRACE
}
[Conditional("DEVTRACE")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
internal void DevTraceVerifyVisibility() {
#if DEVTRACE
if (this.scanSegmentVerify.IsLevel(1)) {
DevTraceInfo(1, "Beginning {0} ScanSegmentTree VisibilityVertex Check..."
, ScanDirection.IsHorizontal ? "Horizontal" : "Vertical");
bool retval = true;
for (RBNode<ScanSegment> segNode = this.segmentTree.TreeMinimum(); null != segNode; segNode = this.segmentTree.Next(segNode)) {
if (null == segNode.Item.LowestVisibilityVertex) {
this.scanSegmentTrace.WriteError(0, "Segment has no VisibilityVertex: {0}", segNode.Item.ToString());
retval = false;
}
}
DevTraceInfo(1, "{0} ScanSegmentTree VisibilityVertex Check complete: status {1}"
, ScanDirection.IsHorizontal ? "Horizontal" : "Vertical", retval ? "pass" : "fail");
Debug.Assert(retval, "One or more ScanSegments do not have a VisibilityVertex");
}
#endif // DEVTRACE
}
#endregion // DevTrace
}
}