mirror of https://github.com/hykilpikonna/AquaDX
226 lines
6.5 KiB
C#
226 lines
6.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using DB;
|
|
using Manager;
|
|
|
|
namespace AquaMai.MaimaiDX2077;
|
|
|
|
public class ParametricSlidePath
|
|
{
|
|
public enum ParseMarker
|
|
{
|
|
None = 0,
|
|
SmoothAlign,
|
|
ForceAlign,
|
|
SharpCorner
|
|
}
|
|
|
|
public abstract class PathSegment
|
|
{
|
|
public ParseMarker ParseMarker = ParseMarker.None;
|
|
public double ArrowDistance = MaiGeometry.DefaultDistance;
|
|
|
|
public abstract bool DoAngleLerp { get; }
|
|
|
|
public abstract Complex GetPointAt(double t);
|
|
|
|
public abstract Complex GetTangentAt(double t);
|
|
|
|
public abstract double GetSegmentLength();
|
|
|
|
public void SetParseMarker(ParseMarker marker) => ParseMarker = marker;
|
|
|
|
public void SetArrowDistance(double distance) => ArrowDistance = distance;
|
|
}
|
|
|
|
public class LineSegment(Complex start, Complex end) : PathSegment
|
|
{
|
|
public readonly Complex StartPoint = start;
|
|
public readonly Complex EndPoint = end;
|
|
|
|
public override bool DoAngleLerp { get; } = false;
|
|
|
|
public override Complex GetPointAt(double t)
|
|
{
|
|
return StartPoint + (EndPoint - StartPoint) * t;
|
|
}
|
|
|
|
public override Complex GetTangentAt(double t)
|
|
{
|
|
var v = EndPoint - StartPoint;
|
|
return v / v.Magnitude;
|
|
}
|
|
|
|
public override double GetSegmentLength()
|
|
{
|
|
return (EndPoint - StartPoint).Magnitude;
|
|
}
|
|
}
|
|
|
|
public class ArcSegment(MaiGeometry.CircleStruct circle, double startAngle, double endAngle) : PathSegment
|
|
{
|
|
public readonly MaiGeometry.CircleStruct Circle = circle;
|
|
public readonly double StartAngle = startAngle;
|
|
public readonly double EndAngle = endAngle;
|
|
|
|
public override bool DoAngleLerp { get; } = true;
|
|
|
|
public override Complex GetPointAt(double t)
|
|
{
|
|
var angle = StartAngle + t * (EndAngle - StartAngle);
|
|
return Circle.Center + Complex.FromPolarCoordinates(Circle.Radius, angle);
|
|
}
|
|
|
|
public override Complex GetTangentAt(double t)
|
|
{
|
|
var angle = StartAngle + t * (EndAngle - StartAngle);
|
|
if (StartAngle < EndAngle)
|
|
{
|
|
return Complex.FromPolarCoordinates(1, angle) * Complex.ImaginaryOne;
|
|
}
|
|
else
|
|
{
|
|
return Complex.FromPolarCoordinates(-1, angle) * Complex.ImaginaryOne;
|
|
}
|
|
}
|
|
|
|
public override double GetSegmentLength()
|
|
{
|
|
return Math.Abs(EndAngle - StartAngle) * Circle.Radius;
|
|
}
|
|
}
|
|
|
|
public class CircleSegment(MaiGeometry.CircleStruct circle, double startAngle, bool isCcw) : PathSegment
|
|
{
|
|
public readonly MaiGeometry.CircleStruct Circle = circle;
|
|
public readonly double StartAngle = startAngle;
|
|
public readonly bool IsCcw = isCcw;
|
|
|
|
public override bool DoAngleLerp { get; } = true;
|
|
|
|
public override Complex GetPointAt(double t)
|
|
{
|
|
double angle;
|
|
if (IsCcw)
|
|
{
|
|
angle = StartAngle + t * Math.PI * 2f;
|
|
}
|
|
else
|
|
{
|
|
angle = StartAngle - t * Math.PI * 2f;
|
|
}
|
|
|
|
return Circle.Center + Complex.FromPolarCoordinates(Circle.Radius, angle);
|
|
}
|
|
|
|
public override Complex GetTangentAt(double t)
|
|
{
|
|
double angle;
|
|
if (IsCcw)
|
|
{
|
|
angle = StartAngle + t * Math.PI * 2f;
|
|
return Complex.FromPolarCoordinates(1, angle) * Complex.ImaginaryOne;
|
|
}
|
|
else
|
|
{
|
|
angle = StartAngle - t * Math.PI * 2f;
|
|
return Complex.FromPolarCoordinates(-1, angle) * Complex.ImaginaryOne;
|
|
}
|
|
}
|
|
|
|
public override double GetSegmentLength()
|
|
{
|
|
return Math.PI * Circle.Radius * 2;
|
|
}
|
|
}
|
|
|
|
|
|
public readonly PathSegment[] Segments;
|
|
public readonly double[] Fractions;
|
|
public readonly double[] AccumulatedLengths;
|
|
|
|
public ParametricSlidePath(IEnumerable<PathSegment> pathSegments)
|
|
{
|
|
Segments = pathSegments.ToArray();
|
|
if (Segments.Length == 0)
|
|
{
|
|
throw new ArgumentException("At least one path segment is required.");
|
|
}
|
|
var lengths = Segments.Select(s => s.GetSegmentLength());
|
|
var sum = 0.0;
|
|
AccumulatedLengths = lengths.Select(x => (sum += x)).ToArray();
|
|
Fractions = AccumulatedLengths.Select(x => x / sum).ToArray();
|
|
}
|
|
|
|
public PathSegment GetSegmentAt(double t, out double segmentT)
|
|
{
|
|
if (t <= 0.0)
|
|
{
|
|
segmentT = 0.0;
|
|
return Segments[0];
|
|
}
|
|
|
|
if (t >= 1.0)
|
|
{
|
|
segmentT = 1.0;
|
|
return Segments[Segments.Length - 1];
|
|
}
|
|
|
|
var idx = Array.BinarySearch(Fractions, t);
|
|
if (idx < 0)
|
|
{
|
|
idx = ~idx; // first entry > t
|
|
}
|
|
// if idx >= 0 then idx is the entry == t
|
|
// so Fractions[idx-1] < t and Fractions[idx] >= t
|
|
// Note: Fractions[i] marks the end point of Segments[i]
|
|
|
|
if (idx >= Segments.Length)
|
|
{
|
|
segmentT = 1.0;
|
|
return Segments[Segments.Length - 1];
|
|
}
|
|
|
|
if (idx == 0)
|
|
{
|
|
segmentT = t / Fractions[0];
|
|
return Segments[0];
|
|
}
|
|
|
|
segmentT = (t - Fractions[idx - 1]) / (Fractions[idx] - Fractions[idx - 1]);
|
|
return Segments[idx];
|
|
}
|
|
|
|
public double GetPathLength() => AccumulatedLengths[AccumulatedLengths.Length - 1];
|
|
|
|
public Complex GetPointAt(double t)
|
|
{
|
|
var segment = GetSegmentAt(t, out var segT);
|
|
return segment.GetPointAt(segT);
|
|
}
|
|
|
|
public Complex GetTangentAt(double t)
|
|
{
|
|
var segment = GetSegmentAt(t, out var segT);
|
|
return segment.GetTangentAt(segT);
|
|
}
|
|
|
|
public SlideType GetEndType(OptionMirrorID mirrorMode)
|
|
{
|
|
var lastSegment = Segments[Segments.Length - 1];
|
|
var flip = mirrorMode == OptionMirrorID.LR || mirrorMode == OptionMirrorID.UD;
|
|
if (lastSegment is CircleSegment circle)
|
|
{
|
|
return circle.IsCcw != flip ? SlideType.Slide_Circle_L : SlideType.Slide_Circle_R;
|
|
}
|
|
|
|
if (lastSegment is ArcSegment arc)
|
|
{
|
|
return (arc.EndAngle > arc.StartAngle) != flip ? SlideType.Slide_Circle_L : SlideType.Slide_Circle_R;
|
|
}
|
|
|
|
return SlideType.Slide_Straight;
|
|
}
|
|
} |