AquaDX/AquaMai/MaimaiDX2077/SlidePathGenerator.cs

132 lines
4.6 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace AquaMai.MaimaiDX2077;
public class SlidePathGenerator
{
public List<ParametricSlidePath.PathSegment> PathSegments = new List<ParametricSlidePath.PathSegment>();
public Complex CurrentEndPoint = Complex.Zero;
public static SlidePathGenerator BeginAt(Complex point)
{
var obj = new SlidePathGenerator();
obj.CurrentEndPoint = point;
return obj;
}
public static double CalcTangentAngle(Complex point, MaiGeometry.CircleStruct circle, bool isCcw)
{
var hypot = point - circle.Center;
var angleDelta = Math.Acos(circle.Radius / hypot.Magnitude);
var tanAngle = hypot.Phase + (isCcw ? angleDelta : -angleDelta);
return Math.IEEERemainder(tanAngle, Math.PI * 2.0);
}
public void TrySetLastParseMarker(ParametricSlidePath.ParseMarker marker)
{
if (PathSegments.Count <= 0) return;
PathSegments[PathSegments.Count - 1].SetParseMarker(marker);
}
public void LineToPoint(Complex point)
{
PathSegments.Add(new ParametricSlidePath.LineSegment(CurrentEndPoint, point));
CurrentEndPoint = point;
}
public void TangentToCircle(MaiGeometry.CircleStruct circle, bool isCcw)
{
var inAngle = CalcTangentAngle(CurrentEndPoint, circle, isCcw);
var inPoint = Complex.FromPolarCoordinates(circle.Radius, inAngle) + circle.Center;
LineToPoint(inPoint);
}
/// <summary>Note: endAngle should be in range [-PI, PI]</summary>
public void ArcToAngle(Complex center, double endAngle, bool isCcw, bool skipIfZero)
{
var diff = CurrentEndPoint - center;
var circle = new MaiGeometry.CircleStruct(center, diff.Magnitude);
var startAngle = diff.Phase;
// startAngle and endAngle in range [-PI, PI]
if (isCcw)
{
if (startAngle > endAngle)
{
startAngle -= 2 * Math.PI;
}
if (Math.Abs(endAngle - startAngle) < 0.001)
{
if (skipIfZero) return;
endAngle += 2 * Math.PI;
}
}
else
{
if (startAngle < endAngle)
{
startAngle += 2 * Math.PI;
}
if (Math.Abs(endAngle - startAngle) < 0.001)
{
if (skipIfZero) return;
endAngle -= 2 * Math.PI;
}
}
var seg = new ParametricSlidePath.ArcSegment(circle, startAngle, endAngle);
PathSegments.Add(seg);
CurrentEndPoint = seg.GetPointAt(1f);
}
public void ArcToTangentTowards(Complex target, Complex center, bool isCcw)
{
var diff = CurrentEndPoint - center;
var endAngle = CalcTangentAngle(target, new MaiGeometry.CircleStruct(center, diff.Magnitude), !isCcw);
ArcToAngle(center, endAngle, isCcw, false);
}
public void FullCircle(Complex center, bool isCcw)
{
var diff = CurrentEndPoint - center;
var circle = new MaiGeometry.CircleStruct(center, diff.Magnitude);
PathSegments.Add(new ParametricSlidePath.CircleSegment(circle, diff.Phase, isCcw));
// CurrentEndPoint not changed
}
public void ExternTangentTransfer(Complex currentCenter, MaiGeometry.CircleStruct targetCircle, bool isCcw)
{
var diff = CurrentEndPoint - currentCenter;
double endAngle;
if (Math.Abs(diff.Magnitude - targetCircle.Radius) < 0.001)
{
// two circles are approximately same radius
var vector = targetCircle.Center - currentCenter;
vector *= isCcw ? -Complex.ImaginaryOne : Complex.ImaginaryOne;
endAngle = vector.Phase;
}
else if (targetCircle.Radius > diff.Magnitude)
{
// target circle larger
var helperCircle = new MaiGeometry.CircleStruct(targetCircle.Center, targetCircle.Radius - diff.Magnitude);
endAngle = CalcTangentAngle(currentCenter, helperCircle, isCcw);
}
else
{
var helperCircle = new MaiGeometry.CircleStruct(currentCenter, diff.Magnitude - targetCircle.Radius);
endAngle = CalcTangentAngle(targetCircle.Center, helperCircle, !isCcw);
}
ArcToAngle(currentCenter, endAngle, isCcw, false);
var inPoint = Complex.FromPolarCoordinates(targetCircle.Radius, endAngle) + targetCircle.Center;
LineToPoint(inPoint);
}
public ParametricSlidePath GeneratePath()
{
return new ParametricSlidePath(PathSegments);
}
}