Aetheria/Assets/Plugins/MSAGL/Routing/Rectilinear/SegmentIntersector.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

308 lines
No EOL
13 KiB
C#

//
// SegmentIntersector.cs
// MSAGL class for intersecting Rectilinear Edge Routing ScanLine segments.
//
// Copyright Microsoft Corporation.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.Msagl.Core.DataStructures;
using Microsoft.Msagl.Core.Geometry;
using Microsoft.Msagl.Routing.Visibility;
namespace Microsoft.Msagl.Routing.Rectilinear {
internal class SegmentIntersector : IComparer<SegmentIntersector.SegEvent>, IComparer<ScanSegment> {
// The event types. We sweep vertically, with a horizontal scanline, so the vertical
// segments that are active and have X coords within the current vertical segment's
// span all create intersections with it. All events are ordered on Y coordinate then X.
internal SegmentIntersector() {
verticalSegmentsScanLine = new RbTree<ScanSegment>(this);
findFirstPred = new Func<ScanSegment, bool>(IsVSegInHSegRange);
}
bool IsVSegInHSegRange(ScanSegment v) {
return PointComparer.Compare(v.Start.X, findFirstHSeg.Start.X) >= 0;
}
// This creates the VisibilityVertex objects along the segments.
internal VisibilityGraph Generate(IEnumerable<ScanSegment> hSegments, IEnumerable<ScanSegment> vSegments) {
foreach (ScanSegment seg in vSegments) {
eventList.Add(new SegEvent(SegEventType.VOpen, seg));
eventList.Add(new SegEvent(SegEventType.VClose, seg));
}
foreach (ScanSegment seg in hSegments) {
eventList.Add(new SegEvent(SegEventType.HOpen, seg));
}
if (0 == eventList.Count) {
return null; // empty
}
eventList.Sort(this);
// Note: We don't need any sentinels in the scanline here, because the lowest VOpen
// events are loaded before the first HOpen is.
// Process all events.
visGraph = VisibilityGraphGenerator.NewVisibilityGraph();
foreach (SegEvent evt in eventList) {
switch (evt.EventType) {
case SegEventType.VOpen:
OnSegmentOpen(evt.Segment);
ScanInsert(evt.Segment);
break;
case SegEventType.VClose:
OnSegmentClose(evt.Segment);
ScanRemove(evt.Segment);
break;
case SegEventType.HOpen:
OnSegmentOpen(evt.Segment);
ScanIntersect(evt.Segment);
break;
default:
Debug.Assert(false, "Unknown SegEventType");
// ReSharper disable HeuristicUnreachableCode
break;
// ReSharper restore HeuristicUnreachableCode
}
} // endforeach
return visGraph;
}
void OnSegmentOpen(ScanSegment seg) {
seg.OnSegmentIntersectorBegin(visGraph);
}
void OnSegmentClose(ScanSegment seg) {
seg.OnSegmentIntersectorEnd(visGraph);
if (null == seg.LowestVisibilityVertex) {
segmentsWithoutVisibility.Add(seg);
}
}
// Scan segments with no visibility will usually be internal to an overlap clump,
// but may be in an external "corner" of intersecting sides for a small enough span
// that no other segment crosses them. In that case we don't need them and they
// would require extra handling later.
internal void RemoveSegmentsWithNoVisibility(ScanSegmentTree horizontalScanSegments,
ScanSegmentTree verticalScanSegments) {
foreach (ScanSegment seg in segmentsWithoutVisibility) {
(seg.IsVertical ? verticalScanSegments : horizontalScanSegments).Remove(seg);
}
}
#region Scanline utilities
void ScanInsert(ScanSegment seg) {
Debug.Assert(null == this.verticalSegmentsScanLine.Find(seg), "seg already exists in the rbtree");
// 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.
verticalSegmentsScanLine.Insert(seg);
}
void ScanRemove(ScanSegment seg) {
verticalSegmentsScanLine.Remove(seg);
}
void ScanIntersect(ScanSegment hSeg) {
// Find the VSeg in the scanline with the lowest X-intersection with HSeg, then iterate
// all VSegs in the scan line after that until we leave the HSeg range.
// We only use FindFirstHSeg in this routine, to find the first satisfying node,
// so we don't care that we leave leftovers in it.
findFirstHSeg = hSeg;
RBNode<ScanSegment> segNode = verticalSegmentsScanLine.FindFirst(findFirstPred);
for (; null != segNode; segNode = verticalSegmentsScanLine.Next(segNode)) {
ScanSegment vSeg = segNode.Item;
if (1 == PointComparer.Compare(vSeg.Start.X, hSeg.End.X)) {
break; // Out of HSeg range
}
VisibilityVertex newVertex = visGraph.AddVertex(new Point(vSeg.Start.X, hSeg.Start.Y));
// HSeg has just opened so if we are overlapped and newVertex already existed,
// it was because we just closed a previous HSeg or VSeg and are now opening one
// whose Start is the same as previous. So we may be appending a vertex that
// is already the *Seg.HighestVisibilityVertex, which will be a no-op. Otherwise
// this will add a (possibly Overlapped)VisibilityEdge in the *Seg direction.
hSeg.AppendVisibilityVertex(visGraph, newVertex);
vSeg.AppendVisibilityVertex(visGraph, newVertex);
} // endforeach scanline VSeg in range
OnSegmentClose(hSeg);
}
// end ScanIntersect()
#endregion // Scanline utilities
#region IComparer<SegEvent>
/// <summary>
/// For ordering events first by Y, then X, then by whether it's an H or V seg.
/// </summary>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns></returns>
public int Compare(SegEvent first, SegEvent second) {
if (first == second) {
return 0;
}
if (first == null) {
return -1;
}
if (second == null) {
return 1;
}
// Unlike the ScanSegment-generating scanline in VisibilityGraphGenerator, this scanline has no slope
// calculations so no additional rounding error is introduced.
int cmp = PointComparer.Compare(first.Site.Y, second.Site.Y);
if (0 != cmp) {
return cmp;
}
// Both are at same Y so we must ensure that for equivalent Y, VClose comes after
// HOpen which comes after VOpen, thus make sure VOpen comes before VClose.
if (first.IsVertical && second.IsVertical) {
// Separate segments may join at Start and End due to overlap.
Debug.Assert(!StaticGraphUtility.IntervalsOverlap(first.Segment, second.Segment)
|| (0 == PointComparer.Compare(first.Segment.Start, second.Segment.End))
|| (0 == PointComparer.Compare(first.Segment.End, second.Segment.Start))
, "V subsumption failure detected in SegEvent comparison");
if (0 == cmp) {
// false is < true.
cmp = (SegEventType.VClose == first.EventType).CompareTo(SegEventType.VClose == second.EventType);
}
return cmp;
}
// If both are H segs, then sub-order by X.
if (!first.IsVertical && !second.IsVertical) {
// Separate segments may join at Start and End due to overlap, so compare by Start.X;
// the ending segment (lowest Start.X) comes before the Open (higher Start.X).
Debug.Assert(!StaticGraphUtility.IntervalsOverlap(first.Segment, second.Segment)
|| (0 == PointComparer.Compare(first.Segment.Start, second.Segment.End))
|| (0 == PointComparer.Compare(first.Segment.End, second.Segment.Start))
, "H subsumption failure detected in SegEvent comparison");
cmp = PointComparer.Compare(first.Site.X, second.Site.X);
return cmp;
}
// One is Vertical and one is Horizontal; we are only interested in the vertical at this point.
SegEvent vEvent = first.IsVertical ? first : second;
// Make sure that we have opened all V segs before and closed them after opening
// an H seg at the same Y coord. Otherwise we'll miss "T" or "corner" intersections.
// (RectilinearTests.Connected_Vertical_Segments_Are_Intersected tests that we get the expected count here.)
// Start assuming Vevent is 'first' and it's VOpen, which should come before HOpen.
cmp = -1; // Start with first == VOpen
if (SegEventType.VClose == vEvent.EventType) {
cmp = 1; // change to first == VClose
}
if (vEvent != first) {
cmp *= -1; // undo the swap.
}
return cmp;
}
#endregion // IComparer<SegEvent>
#region IComparer<ScanSegment>
/// <summary>
/// For ordering V segments in the scanline by X.
/// </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;
}
// Note: Unlike the ScanSegment-generating scanline, this scanline has no slope
// calculations so no additional rounding error is introduced.
int cmp = PointComparer.Compare(first.Start.X, second.Start.X);
// Separate segments may join at Start and End due to overlap, so compare the Y positions;
// the Close (lowest Y) comes before the Open.
if (0 == cmp) {
cmp = PointComparer.Compare(first.Start.Y, second.Start.Y);
}
return cmp;
}
#endregion // IComparer<ScanSegment>
#region Nested type: SegEvent
internal class SegEvent {
internal SegEvent(SegEventType eventType, ScanSegment seg) {
EventType = eventType;
Segment = seg;
}
internal SegEventType EventType { get; private set; }
internal ScanSegment Segment { get; private set; }
internal bool IsVertical {
get { return (SegEventType.HOpen != EventType); }
}
internal Point Site {
get { return (SegEventType.VClose == EventType) ? Segment.End : Segment.Start; }
}
/// <summary>
/// </summary>
/// <returns></returns>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object[])")]
public override string ToString() {
return string.Format("{0} {1} {2} {3}", EventType, IsVertical, Site, Segment);
}
}
#endregion
#region Internal data members
// To be returned to caller; created in Generate() and used in ScanGenerate().
// Accumulates the set of events and then sorts them by Y coord.
readonly List<SegEvent> eventList = new List<SegEvent>();
// Tracks the currently open V segments.
readonly Func<ScanSegment, bool> findFirstPred;
readonly List<ScanSegment> segmentsWithoutVisibility = new List<ScanSegment>();
readonly RbTree<ScanSegment> verticalSegmentsScanLine;
// For searching the tree to find the first VSeg for an HSeg.
ScanSegment findFirstHSeg;
VisibilityGraph visGraph;
#endregion // Internal data members
#region Nested type: SegEventType
internal enum SegEventType {
VOpen // Vertical segment start (bottom)
,
VClose // Vertical segment close (top)
,
HOpen // Horizontal segment open (flat, uses left point but could be either)
}
#endregion
}
}