1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 16:02:55 +08:00

Fix bulbs on Catmull sliders

This commit is contained in:
Dan Balasescu 2024-03-27 04:05:04 +09:00
parent 6ef54321c4
commit 600098d845
No known key found for this signature in database

View File

@ -42,6 +42,17 @@ namespace osu.Game.Rulesets.Objects
private readonly List<double> cumulativeLength = new List<double>();
private readonly Cached pathCache = new Cached();
/// <summary>
/// Any additional length of the path which was optimised out during piecewise approximation, but should still be considered as part of <see cref="calculatedLength"/>.
/// </summary>
/// <remarks>
/// This is a hack for Catmull paths.
/// </remarks>
private double optimisedLength;
/// <summary>
/// The final calculated length of the path.
/// </summary>
private double calculatedLength;
private readonly List<int> segmentEnds = new List<int>();
@ -244,6 +255,7 @@ namespace osu.Game.Rulesets.Objects
{
calculatedPath.Clear();
segmentEnds.Clear();
optimisedLength = 0;
if (ControlPoints.Count == 0)
return;
@ -268,7 +280,8 @@ namespace osu.Game.Rulesets.Objects
calculatedPath.Add(segmentVertices[0]);
else if (segmentVertices.Length > 1)
{
List<Vector2> subPath = calculateSubPath(segmentVertices, segmentType);
List<Vector2> subPath = calculateSubPath(segmentVertices, segmentType, ref optimisedLength);
// Skip the first vertex if it is the same as the last vertex from the previous segment
bool skipFirst = calculatedPath.Count > 0 && subPath.Count > 0 && calculatedPath.Last() == subPath[0];
@ -287,7 +300,7 @@ namespace osu.Game.Rulesets.Objects
}
}
private List<Vector2> calculateSubPath(ReadOnlySpan<Vector2> subControlPoints, PathType type)
private static List<Vector2> calculateSubPath(ReadOnlySpan<Vector2> subControlPoints, PathType type, ref double optimisedLength)
{
switch (type.Type)
{
@ -295,6 +308,7 @@ namespace osu.Game.Rulesets.Objects
return PathApproximator.LinearToPiecewiseLinear(subControlPoints);
case SplineType.PerfectCurve:
{
if (subControlPoints.Length != 3)
break;
@ -305,9 +319,55 @@ namespace osu.Game.Rulesets.Objects
break;
return subPath;
}
case SplineType.Catmull:
return PathApproximator.CatmullToPiecewiseLinear(subControlPoints);
{
List<Vector2> subPath = PathApproximator.CatmullToPiecewiseLinear(subControlPoints);
// At draw time, osu!stable optimises paths by only keeping piecewise segments that are 6px apart.
// For the most part we don't care about this optimisation, and its additional heuristics are hard to reproduce in every implementation.
//
// However, it matters for Catmull paths which form "bulbs" around sequential knots with identical positions,
// so we'll apply a very basic form of the optimisation here and return a length representing the optimised portion.
// The returned length is important so that the optimisation doesn't cause the path to get extended to match the value of ExpectedDistance.
List<Vector2> optimisedPath = new List<Vector2>(subPath.Count);
Vector2? lastStart = null;
double lengthRemovedSinceStart = 0;
for (int i = 0; i < subPath.Count; i++)
{
if (lastStart == null)
{
optimisedPath.Add(subPath[i]);
lastStart = subPath[i];
continue;
}
Debug.Assert(i > 0);
double distFromStart = Vector2.Distance(lastStart.Value, subPath[i]);
lengthRemovedSinceStart += Vector2.Distance(subPath[i - 1], subPath[i]);
// See PathApproximator.catmull_detail.
const int catmull_detail = 50;
const int catmull_segment_length = catmull_detail * 2;
// Either 6px from the start, the last vertex at every knot, or the end of the path.
if (distFromStart > 6 || (i + 1) % catmull_segment_length == 0 || i == subPath.Count - 1)
{
optimisedPath.Add(subPath[i]);
optimisedLength += lengthRemovedSinceStart - distFromStart;
lastStart = null;
lengthRemovedSinceStart = 0;
}
}
return optimisedPath;
}
}
return PathApproximator.BSplineToPiecewiseLinear(subControlPoints, type.Degree ?? subControlPoints.Length);
@ -315,7 +375,7 @@ namespace osu.Game.Rulesets.Objects
private void calculateLength()
{
calculatedLength = 0;
calculatedLength = optimisedLength;
cumulativeLength.Clear();
cumulativeLength.Add(0);