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
271 lines
13 KiB
C#
271 lines
13 KiB
C#
//
|
|
// RectilinearScanLine.cs
|
|
// MSAGL ScanLine class for Rectilinear Edge Routing line generation.
|
|
//
|
|
// Copyright Microsoft Corporation.
|
|
using System.Diagnostics;
|
|
using System.Collections.Generic;
|
|
using Microsoft.Msagl.Core.DataStructures;
|
|
using Microsoft.Msagl.Core.Geometry;
|
|
using Microsoft.Msagl.Core.Geometry.Curves;
|
|
using Microsoft.Msagl.Core.Layout;
|
|
using Microsoft.Msagl.DebugHelpers;
|
|
using Microsoft.Msagl.Core;
|
|
|
|
namespace Microsoft.Msagl.Routing.Rectilinear {
|
|
internal class RectilinearScanLine : IComparer<BasicObstacleSide> {
|
|
readonly ScanDirection scanDirection;
|
|
|
|
// This is the data structure that allows fast insert/remove of obstacle edges as well as
|
|
// scanning for next/prev edges along the direction of the scan line.
|
|
RbTree<BasicObstacleSide> SideTree { get; set; }
|
|
|
|
// Because sides may overlap and thus their relative positions change, retain the current
|
|
// position, which is set on insertions by parameter, and by Overlap events via SetLinePosition.
|
|
private Point linePositionAtLastInsertOrRemove;
|
|
|
|
internal RectilinearScanLine(ScanDirection scanDir, Point start) {
|
|
scanDirection = scanDir;
|
|
SideTree = new RbTree<BasicObstacleSide>(this);
|
|
this.linePositionAtLastInsertOrRemove = start;
|
|
}
|
|
|
|
internal RBNode<BasicObstacleSide> Insert(BasicObstacleSide side, Point scanPos) {
|
|
DevTraceInfo(1, "prev LinePos = {0}, new LinePos = {1}, inserting side = {2}", this.linePositionAtLastInsertOrRemove, scanPos, side.ToString());
|
|
Assert(!scanDirection.IsFlat(side), "Flat sides are not allowed in the scanline");
|
|
Assert(null == Find(side), "side already exists in the ScanLine");
|
|
this.linePositionAtLastInsertOrRemove = scanPos;
|
|
|
|
// 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.
|
|
var node = SideTree.Insert(side);
|
|
DevTraceDump(2);
|
|
return node;
|
|
}
|
|
|
|
internal int Count { get { return SideTree.Count; } }
|
|
|
|
internal void Remove(BasicObstacleSide side, Point scanPos) {
|
|
DevTraceInfo(1, "current linePos = {0}, removing side = {1}", this.linePositionAtLastInsertOrRemove, side.ToString());
|
|
Assert(null != Find(side), "side does not exist in the ScanLine");
|
|
this.linePositionAtLastInsertOrRemove = scanPos;
|
|
SideTree.Remove(side);
|
|
DevTraceDump(2);
|
|
}
|
|
|
|
internal RBNode<BasicObstacleSide> Find(BasicObstacleSide side) {
|
|
// Sides that start after the current position cannot be in the scanline.
|
|
if (-1 == scanDirection.ComparePerpCoord(this.linePositionAtLastInsertOrRemove, side.Start)) {
|
|
return null;
|
|
}
|
|
return SideTree.Find(side);
|
|
}
|
|
|
|
internal RBNode<BasicObstacleSide> NextLow(BasicObstacleSide side) {
|
|
return NextLow(Find(side));
|
|
}
|
|
|
|
internal RBNode<BasicObstacleSide> NextLow(RBNode<BasicObstacleSide> sideNode) {
|
|
var pred = SideTree.Previous(sideNode);
|
|
return pred;
|
|
}
|
|
|
|
internal RBNode<BasicObstacleSide> NextHigh(BasicObstacleSide side) {
|
|
return NextHigh(Find(side));
|
|
}
|
|
|
|
internal RBNode<BasicObstacleSide> NextHigh(RBNode<BasicObstacleSide> sideNode) {
|
|
var succ = SideTree.Next(sideNode);
|
|
return succ;
|
|
}
|
|
|
|
internal RBNode<BasicObstacleSide> Next(Directions dir, RBNode<BasicObstacleSide> sideNode) {
|
|
var succ = (StaticGraphUtility.IsAscending(dir)) ? SideTree.Next(sideNode) : SideTree.Previous(sideNode);
|
|
return succ;
|
|
}
|
|
|
|
internal RBNode<BasicObstacleSide> Lowest()
|
|
{
|
|
return SideTree.TreeMinimum();
|
|
}
|
|
|
|
#if DEVTRACE
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
|
|
// ReSharper disable InconsistentNaming
|
|
internal void DevTrace_VerifyConsistency(string descFormat, params object[] descArgs) {
|
|
// ReSharper restore InconsistentNaming
|
|
bool retval = true;
|
|
if (scanLineVerify.IsLevel(1)) {
|
|
DevTraceInfo(2, "ScanLineConsistencyCheck LinePos = {0}", this.linePositionAtLastInsertOrRemove);
|
|
BasicObstacleSide prevSide = null;
|
|
DevTraceInfo(3, "ScanLine dump {0}, count = {1}:", string.Format(descFormat, descArgs), SideTree.Count);
|
|
foreach (var currentSide in SideTree) {
|
|
DevTraceInfo(3, currentSide.ToString());
|
|
if ((null != prevSide) && (-1 != Compare(prevSide, currentSide))) {
|
|
scanLineTrace.WriteError(0, "Sides are not strictly increasing:");
|
|
scanLineTrace.WriteFollowup(0, prevSide.ToString());
|
|
scanLineTrace.WriteFollowup(0, currentSide.ToString());
|
|
retval = false;
|
|
}
|
|
prevSide = currentSide;
|
|
}
|
|
}
|
|
Assert(retval, "Sides are not strictly increasing");
|
|
}
|
|
#endif // DEVTRACE
|
|
#region IComparer<BasicObstacleSide>
|
|
/// <summary>
|
|
/// For ordering lines along the scanline at segment starts/ends.
|
|
/// </summary>
|
|
/// <param name="first"></param>
|
|
/// <param name="second"></param>
|
|
/// <returns></returns>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
|
|
public int Compare(BasicObstacleSide first, BasicObstacleSide second) {
|
|
ValidateArg.IsNotNull(first, "first");
|
|
ValidateArg.IsNotNull(second, "second");
|
|
|
|
// If these are two sides of the same obstacle then the ordering is obvious.
|
|
if (first.Obstacle == second.Obstacle) {
|
|
if (first == second) {
|
|
return 0;
|
|
}
|
|
return (first is LowObstacleSide) ? -1 : 1;
|
|
}
|
|
|
|
Debug_VerifySidesDoNotIntersect(first, second);
|
|
|
|
// Other than intersecting sides at vertices of the same obstacle, there should be no interior intersections...
|
|
Point firstIntersect = VisibilityGraphGenerator.ScanLineIntersectSide(this.linePositionAtLastInsertOrRemove, first, scanDirection);
|
|
Point secondIntersect = VisibilityGraphGenerator.ScanLineIntersectSide(this.linePositionAtLastInsertOrRemove, second, scanDirection);
|
|
var cmp = firstIntersect.CompareTo(secondIntersect);
|
|
|
|
// ... but we may still have rectangular sides that coincide, or angled sides that are close enough here but
|
|
// are not detected by the convex-hull overlap calculations. In those cases, we refine the comparison by side
|
|
// type, with High coming before Low, and then by obstacle ordinal if needed. Because there are no interior
|
|
// intersections, this ordering will remain valid as long as the side(s) are in the scanline.
|
|
if (0 == cmp) {
|
|
bool firstIsLow = first is LowObstacleSide;
|
|
bool secondIsLow = second is LowObstacleSide;
|
|
cmp = firstIsLow.CompareTo(secondIsLow);
|
|
if (0 == cmp) {
|
|
cmp = first.Obstacle.Ordinal.CompareTo(second.Obstacle.Ordinal);
|
|
}
|
|
}
|
|
|
|
DevTraceInfo(4, "Compare {0} @ {1:F5} {2:F5} and {3:F5} {4:F5}: {5} {6}",
|
|
cmp, firstIntersect.X, firstIntersect.Y, secondIntersect.X, secondIntersect.Y, first, second);
|
|
return cmp;
|
|
}
|
|
|
|
[Conditional("DEBUG")]
|
|
internal static void Debug_VerifySidesDoNotIntersect(BasicObstacleSide side1, BasicObstacleSide side2) {
|
|
Point intersect;
|
|
if (!Point.LineLineIntersection(side1.Start, side1.End, side2.Start, side2.End, out intersect)) {
|
|
return;
|
|
}
|
|
|
|
// The test for being within the interval is just multiplying to ensure that both subtractions
|
|
// return same-signed results (including endpoints).
|
|
var isInterior = ((side1.Start - intersect) * (intersect - side1.End) >= -ApproximateComparer.DistanceEpsilon)
|
|
&& ((side2.Start - intersect) * (intersect - side2.End) >= -ApproximateComparer.DistanceEpsilon);
|
|
Debug.Assert(!isInterior, "Shouldn't have interior intersections except sides of the same obstacle");
|
|
}
|
|
|
|
#endregion // IComparer<BasicObstacleSide>
|
|
|
|
/// <summary>
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public override string ToString() {
|
|
return this.linePositionAtLastInsertOrRemove + " " + scanDirection;
|
|
}
|
|
|
|
[Conditional("DEBUG")]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
|
|
void Assert(bool condition, string message) {
|
|
// #if TEST_MSAGL
|
|
// if (!condition) {
|
|
// Test_DumpScanLine();
|
|
// }
|
|
// #endif // TEST
|
|
Debug.Assert(condition, message);
|
|
}
|
|
|
|
#region DevTrace
|
|
#if DEVTRACE
|
|
readonly DevTrace scanLineTrace = new DevTrace("Rectilinear_ScanLineTrace", "RectScanLine");
|
|
readonly DevTrace scanLineDump = new DevTrace("Rectilinear_ScanLineDump");
|
|
readonly DevTrace scanLineVerify = new DevTrace("Rectilinear_ScanLineVerify");
|
|
#endif // DEVTRACE
|
|
|
|
[Conditional("DEVTRACE")]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
|
|
void DevTraceInfo(int verboseLevel, string format, params object[] args) {
|
|
#if DEVTRACE
|
|
scanLineTrace.WriteLineIf(DevTrace.Level.Info, verboseLevel, format, args);
|
|
#endif // DEVTRACE
|
|
}
|
|
|
|
[Conditional("DEVTRACE")]
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
|
|
void DevTraceDump(int verboseLevel) {
|
|
#if DEVTRACE
|
|
if (scanLineDump.IsLevel(verboseLevel)) {
|
|
Test_DumpScanLine();
|
|
}
|
|
#endif // DEVTRACE
|
|
}
|
|
#endregion // DevTrace
|
|
|
|
#region DebugCurves
|
|
|
|
// #if TEST_MSAGL
|
|
// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
|
|
// internal void Test_DumpScanLine() {
|
|
// DebugCurveCollection.WriteToFile(Test_GetScanLineDebugCurves(), StaticGraphUtility.GetDumpFileName("ScanLine"));
|
|
// }
|
|
// #endif // TEST
|
|
|
|
// #if TEST_MSAGL
|
|
// internal List<DebugCurve> Test_GetScanLineDebugCurves() {
|
|
// // ReSharper restore InconsistentNaming
|
|
// var debugCurves = new List<DebugCurve>();
|
|
//
|
|
// // Alternate the colors between green and blue, so that any inconsistency will stand out.
|
|
// // Use red to highlight that.
|
|
// string[] colors = { "green", "blue" };
|
|
// int index = 0;
|
|
// var bbox = new Rectangle();
|
|
// BasicObstacleSide prevSide = null;
|
|
// foreach (var currentSide in SideTree) {
|
|
// string color = colors[index];
|
|
// index ^= 1;
|
|
// if (null == prevSide) {
|
|
// // Create this the first time through; adding to an empty rectangle leaves 0,0.
|
|
// bbox = new Rectangle(currentSide.Start, currentSide.End);
|
|
// }
|
|
// else {
|
|
// if (-1 != Compare(prevSide, currentSide)) {
|
|
// // Note: we toggled the index, so the red replaces the colour whose turn it is now
|
|
// // and will leave the red line bracketed by two sides of the same colour.
|
|
// color = "red";
|
|
// }
|
|
// bbox.Add(currentSide.Start);
|
|
// bbox.Add(currentSide.End);
|
|
// }
|
|
// debugCurves.Add(new DebugCurve(0.1, color, new LineSegment(currentSide.Start, currentSide.End)));
|
|
// prevSide = currentSide;
|
|
// }
|
|
//
|
|
// // Add the sweep line.
|
|
// Point start = StaticGraphUtility.RectangleBorderIntersect(bbox, this.linePositionAtLastInsertOrRemove, scanDirection.OppositeDirection);
|
|
// Point end = StaticGraphUtility.RectangleBorderIntersect(bbox, this.linePositionAtLastInsertOrRemove, scanDirection.Direction);
|
|
// debugCurves.Add(new DebugCurve(0.025, "black", new LineSegment(start, end)));
|
|
// return debugCurves;
|
|
// }
|
|
// #endif // TEST
|
|
#endregion // DebugCurves
|
|
}
|
|
}
|