Aetheria/Assets/Plugins/MSAGL/Routing/Spline/Bundling/BundleInfo.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

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));
}
}
}