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
307 lines
14 KiB
C#
307 lines
14 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Microsoft.Msagl.Core.DataStructures;
|
|
using Microsoft.Msagl.Core.Geometry;
|
|
using Microsoft.Msagl.Core.Geometry.Curves;
|
|
using System.Diagnostics;
|
|
using System;
|
|
using Microsoft.Msagl.DebugHelpers;
|
|
using Microsoft.Msagl.Core.Layout;
|
|
|
|
namespace Microsoft.Msagl.Routing.Spline.Bundling {
|
|
internal class BundleInfo {
|
|
const double FeasibleWidthEpsilon = 0.1; //??
|
|
|
|
internal readonly BundleBase SourceBase;
|
|
internal readonly BundleBase TargetBase;
|
|
readonly Set<Polyline> obstaclesToIgnore;
|
|
readonly internal double EdgeSeparation;
|
|
readonly internal double[] HalfWidthArray;
|
|
readonly double longEnoughSideLength;
|
|
|
|
List<Polyline> tightObstaclesInTheBoundingBox;
|
|
internal double TotalRequiredWidth;
|
|
|
|
internal BundleInfo(BundleBase sourceBase, BundleBase targetBase, Set<Polyline> obstaclesToIgnore, double edgeSeparation, double[] halfWidthArray) {
|
|
SourceBase = sourceBase;
|
|
TargetBase = targetBase;
|
|
this.obstaclesToIgnore = obstaclesToIgnore;
|
|
EdgeSeparation = edgeSeparation;
|
|
HalfWidthArray = halfWidthArray;
|
|
TotalRequiredWidth = EdgeSeparation * HalfWidthArray.Length + HalfWidthArray.Sum() * 2;
|
|
longEnoughSideLength = new Rectangle(sourceBase.Curve.BoundingBox, targetBase.Curve.BoundingBox).Diagonal;
|
|
|
|
//sometimes TotalRequiredWidth is too large to fit into the circle, so we evenly scale everything
|
|
double mn = Math.Max(sourceBase.Curve.BoundingBox.Diagonal, targetBase.Curve.BoundingBox.Diagonal);
|
|
if (TotalRequiredWidth > mn) {
|
|
double scale = TotalRequiredWidth / mn;
|
|
for (int i = 0; i < HalfWidthArray.Length; i++)
|
|
HalfWidthArray[i] /= scale;
|
|
TotalRequiredWidth /= scale;
|
|
EdgeSeparation /= scale;
|
|
}
|
|
}
|
|
|
|
internal void SetParamsFeasiblySymmetrically(RectangleNode<Polyline> tightTree) {
|
|
CalculateTightObstaclesForBundle(tightTree, obstaclesToIgnore);
|
|
SetEndParamsSymmetrically();
|
|
}
|
|
|
|
void CalculateTightObstaclesForBundle(RectangleNode<Polyline> tightTree, Set<Polyline> obstaclesToIgnore) {
|
|
double sRadius = SourceBase.Curve.BoundingBox.Diagonal / 2;
|
|
double tRadius = TargetBase.Curve.BoundingBox.Diagonal / 2;
|
|
|
|
//Polyline bundle = Intersections.Create4gon(SourceBase.CurveCenter, TargetBase.CurveCenter, sRadius * 2, tRadius * 2);
|
|
Polyline bundle = Intersections.Create4gon(SourceBase.Position, TargetBase.Position, sRadius * 2, tRadius * 2);
|
|
|
|
tightObstaclesInTheBoundingBox = tightTree.AllHitItems(bundle.BoundingBox,
|
|
p => !obstaclesToIgnore.Contains(p) && Curve.ClosedCurveInteriorsIntersect(bundle, p)).ToList();
|
|
}
|
|
|
|
void SetEndParamsSymmetrically() {
|
|
Point targetPos = TargetBase.Position;
|
|
Point sourcePos = SourceBase.Position;
|
|
|
|
var dir = (targetPos - sourcePos).Normalize();
|
|
var perp = dir.Rotate90Ccw();
|
|
var middle = 0.5 * (targetPos + sourcePos);
|
|
var a = middle + longEnoughSideLength * dir;
|
|
var b = middle - longEnoughSideLength * dir; // [a,b] is a long enough segment
|
|
|
|
//we are already fine
|
|
if (SetRLParamsIfWidthIsFeasible(TotalRequiredWidth * perp / 2, a, b)) {
|
|
SetInitialMidParams();
|
|
return;
|
|
}
|
|
|
|
//find the segment using binary search
|
|
var uw = TotalRequiredWidth;
|
|
var lw = 0.0;
|
|
var mw = uw / 2;
|
|
while (uw - lw > FeasibleWidthEpsilon) {
|
|
if (SetRLParamsIfWidthIsFeasible(mw * perp / 2, a, b))
|
|
lw = mw;
|
|
else
|
|
uw = mw;
|
|
mw = 0.5 * (uw + lw);
|
|
}
|
|
|
|
if (mw <= FeasibleWidthEpsilon) {
|
|
//try one side
|
|
if (SetRLParamsIfWidthIsFeasible(2 * FeasibleWidthEpsilon * perp / 2, new Point(), a, b)) {
|
|
mw = 2 * FeasibleWidthEpsilon;
|
|
}
|
|
else if (SetRLParamsIfWidthIsFeasible(new Point(), -2 * FeasibleWidthEpsilon * perp / 2, a, b)) {
|
|
mw = 2 * FeasibleWidthEpsilon;
|
|
}
|
|
}
|
|
|
|
Debug.Assert(mw > FeasibleWidthEpsilon);
|
|
|
|
SetInitialMidParams();
|
|
}
|
|
|
|
bool SetRLParamsIfWidthIsFeasible(Point perp, Point a, Point b) {
|
|
return SetRLParamsIfWidthIsFeasible(perp, -perp, a, b);
|
|
}
|
|
|
|
bool SetRLParamsIfWidthIsFeasible(Point perpL, Point perpR, Point a, Point b) {
|
|
double sourceRParam, targetRParam, sourceLParam, targetLParam;
|
|
var ls = TrimSegWithBoundaryCurves(new LineSegment(a + perpL, b + perpL), out sourceLParam, out targetRParam);
|
|
if (ls == null)
|
|
return false;
|
|
if (tightObstaclesInTheBoundingBox.Any(t => Intersections.LineSegmentIntersectPolyline(ls.Start, ls.End, t)))
|
|
return false;
|
|
|
|
ls = TrimSegWithBoundaryCurves(new LineSegment(a + perpR, b + perpR), out sourceRParam, out targetLParam);
|
|
if (ls == null)
|
|
return false;
|
|
if (tightObstaclesInTheBoundingBox.Any(t => Intersections.LineSegmentIntersectPolyline(ls.Start, ls.End, t)))
|
|
return false;
|
|
|
|
if (SourceBase.IsParent) {
|
|
SourceBase.ParRight = sourceLParam;
|
|
SourceBase.ParLeft = sourceRParam;
|
|
}
|
|
else {
|
|
SourceBase.ParRight = sourceRParam;
|
|
SourceBase.ParLeft = sourceLParam;
|
|
}
|
|
|
|
//SourceBase.InitialMidParameter = SourceBase.AdjustParam(SourceBase.ParRight + SourceBase.Span / 2);
|
|
|
|
if (TargetBase.IsParent) {
|
|
TargetBase.ParRight = targetLParam;
|
|
TargetBase.ParLeft = targetRParam;
|
|
}
|
|
else {
|
|
TargetBase.ParRight = targetRParam;
|
|
TargetBase.ParLeft = targetLParam;
|
|
}
|
|
|
|
//TargetBase.InitialMidParameter = TargetBase.AdjustParam(TargetBase.ParRight + TargetBase.Span / 2);
|
|
|
|
return true;
|
|
}
|
|
|
|
void SetInitialMidParams() {
|
|
double sourceParam, targetParam;
|
|
var ls = TrimSegWithBoundaryCurves(new LineSegment(TargetBase.CurveCenter, SourceBase.CurveCenter), out sourceParam, out targetParam);
|
|
if (ls != null) {
|
|
SourceBase.InitialMidParameter = sourceParam;
|
|
TargetBase.InitialMidParameter = targetParam;
|
|
}
|
|
else {
|
|
SourceBase.InitialMidParameter = SourceBase.AdjustParam(SourceBase.ParRight + SourceBase.Span / 2);
|
|
TargetBase.InitialMidParameter = TargetBase.AdjustParam(TargetBase.ParRight + TargetBase.Span / 2);
|
|
}
|
|
}
|
|
|
|
LineSegment TrimSegWithBoundaryCurves(LineSegment ls, out double sourcePar, out double targetPar) {
|
|
//ls goes from target to source
|
|
var inters = Curve.GetAllIntersections(ls, SourceBase.Curve, true);
|
|
if (inters.Count == 0) {
|
|
sourcePar = targetPar = 0;
|
|
return null;
|
|
}
|
|
IntersectionInfo i0;
|
|
if (inters.Count == 1)
|
|
i0 = inters[0];
|
|
else {
|
|
if (!SourceBase.IsParent)
|
|
i0 = inters[0].Par0 < inters[1].Par0 ? inters[0] : inters[1];
|
|
else
|
|
i0 = inters[0].Par0 < inters[1].Par0 ? inters[1] : inters[0];
|
|
}
|
|
|
|
inters = Curve.GetAllIntersections(ls, TargetBase.Curve, true);
|
|
if (inters.Count == 0) {
|
|
sourcePar = targetPar = 0;
|
|
return null;
|
|
}
|
|
IntersectionInfo i1;
|
|
if (inters.Count == 1)
|
|
i1 = inters[0];
|
|
else {
|
|
if (!TargetBase.IsParent)
|
|
i1 = inters[0].Par0 > inters[1].Par0 ? inters[0] : inters[1];
|
|
else
|
|
i1 = inters[0].Par0 > inters[1].Par0 ? inters[1] : inters[0];
|
|
}
|
|
|
|
sourcePar = i0.Par1;
|
|
targetPar = i1.Par1;
|
|
return new LineSegment(i0.IntersectionPoint, i1.IntersectionPoint);
|
|
}
|
|
|
|
internal void RotateBy(int rotationOfSourceRightPoint, int rotationOfSourceLeftPoint, int rotationOfTargetRightPoint, int rotationOfTargetLeftPoint, double parameterChange) {
|
|
bool needToUpdateSource = rotationOfSourceRightPoint != 0 || rotationOfSourceLeftPoint != 0;
|
|
bool needToUpdateTarget = rotationOfTargetRightPoint != 0 || rotationOfTargetLeftPoint != 0;
|
|
|
|
if (needToUpdateSource)
|
|
SourceBase.RotateBy(rotationOfSourceRightPoint, rotationOfSourceLeftPoint, parameterChange);
|
|
|
|
if (needToUpdateTarget)
|
|
TargetBase.RotateBy(rotationOfTargetRightPoint, rotationOfTargetLeftPoint, parameterChange);
|
|
|
|
UpdateSourceAndTargetBases(needToUpdateSource, needToUpdateTarget);
|
|
}
|
|
|
|
internal void UpdateSourceAndTargetBases(bool sourceChanged, bool targetChanged) {
|
|
if (sourceChanged)
|
|
UpdatePointsOnSourceBase();
|
|
if (targetChanged)
|
|
UpdatePointsOnTargetBase();
|
|
|
|
UpdateTangentsOnBases();
|
|
}
|
|
|
|
private void UpdateTangentsOnBases() {
|
|
int count = TargetBase.Count;
|
|
//updating tangents
|
|
for (int i = 0; i < count; i++) {
|
|
Point d = TargetBase.Points[i] - SourceBase.Points[count - 1 - i];
|
|
double len = d.Length;
|
|
if (len >= ApproximateComparer.Tolerance) {
|
|
d /= len;
|
|
TargetBase.Tangents[i] = d;
|
|
SourceBase.Tangents[count - 1 - i] = d.Negate();
|
|
}
|
|
}
|
|
}
|
|
|
|
void UpdatePointsOnSourceBase() {
|
|
int count = SourceBase.Count;
|
|
|
|
Point[] pns = SourceBase.Points;
|
|
Point dir = (SourceBase.RightPoint - SourceBase.LeftPoint) / TotalRequiredWidth;
|
|
|
|
pns[count - 1] = SourceBase.LeftPoint + (EdgeSeparation / 2 + HalfWidthArray[0]) * dir;
|
|
int j = 1;
|
|
for (int i = count - 2; i >= 0; i--, j++)
|
|
pns[i] = pns[i + 1] + (HalfWidthArray[j - 1] + EdgeSeparation + HalfWidthArray[j]) * dir;
|
|
}
|
|
|
|
void UpdatePointsOnTargetBase() {
|
|
int count = TargetBase.Count;
|
|
|
|
Point[] pns = TargetBase.Points;
|
|
Point dir = (TargetBase.LeftPoint - TargetBase.RightPoint) / TotalRequiredWidth;
|
|
|
|
pns[0] = TargetBase.RightPoint + (EdgeSeparation / 2 + HalfWidthArray[0]) * dir;
|
|
for (int i = 1; i < count; i++)
|
|
pns[i] = pns[i - 1] + (HalfWidthArray[i - 1] + EdgeSeparation + HalfWidthArray[i]) * dir;
|
|
}
|
|
|
|
internal bool RotationIsLegal(int rotationOfSourceRightPoint, int rotationOfSourceLeftPoint,
|
|
int rotationOfTargetRightPoint, int rotationOfTargetLeftPoint, double parameterChange) {
|
|
//1. we can't have intersections with obstacles
|
|
//(we check borderlines of the bundle only)
|
|
if (!SourceBase.IsParent && !TargetBase.IsParent) {
|
|
if (rotationOfSourceLeftPoint != 0 || rotationOfTargetRightPoint != 0) {
|
|
Point rSoP = SourceBase.RotateLeftPoint(rotationOfSourceLeftPoint, parameterChange);
|
|
Point lTarP = TargetBase.RotateRigthPoint(rotationOfTargetRightPoint, parameterChange);
|
|
if (!LineIsLegal(rSoP, lTarP))
|
|
return false;
|
|
}
|
|
|
|
if (rotationOfSourceRightPoint != 0 || rotationOfTargetLeftPoint != 0) {
|
|
Point lSoP = SourceBase.RotateRigthPoint(rotationOfSourceRightPoint, parameterChange);
|
|
Point rTarP = TargetBase.RotateLeftPoint(rotationOfTargetLeftPoint, parameterChange);
|
|
if (!LineIsLegal(lSoP, rTarP))
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
if (rotationOfSourceLeftPoint != 0 || rotationOfTargetLeftPoint != 0) {
|
|
Point lSoP = SourceBase.RotateLeftPoint(rotationOfSourceLeftPoint, parameterChange);
|
|
Point lTarP = TargetBase.RotateLeftPoint(rotationOfTargetLeftPoint, parameterChange);
|
|
if (!LineIsLegal(lSoP, lTarP))
|
|
return false;
|
|
}
|
|
|
|
if (rotationOfSourceRightPoint != 0 || rotationOfTargetRightPoint != 0) {
|
|
Point rSoP = SourceBase.RotateRigthPoint(rotationOfSourceRightPoint, parameterChange);
|
|
Point rTarP = TargetBase.RotateRigthPoint(rotationOfTargetRightPoint, parameterChange);
|
|
if (!LineIsLegal(rSoP, rTarP))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//2. we are also not allowed to change the order of bundles around a hub
|
|
if (rotationOfSourceRightPoint != 0 || rotationOfSourceLeftPoint != 0)
|
|
if (!SourceBase.RelativeOrderOfBasesIsPreserved(rotationOfSourceRightPoint, rotationOfSourceLeftPoint, parameterChange))
|
|
return false;
|
|
|
|
if (rotationOfTargetRightPoint != 0 || rotationOfTargetLeftPoint != 0)
|
|
if (!TargetBase.RelativeOrderOfBasesIsPreserved(rotationOfTargetRightPoint, rotationOfTargetLeftPoint, parameterChange))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool LineIsLegal(Point a, Point b) {
|
|
return tightObstaclesInTheBoundingBox.All(t => !Intersections.LineSegmentIntersectPolyline(a, b, t));
|
|
}
|
|
}
|
|
}
|