1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 09:02:55 +08:00

Fix multi-segment sliders not parsing correctly

This commit is contained in:
smoogipoo 2020-10-12 18:04:28 +09:00
parent e70d261474
commit 48c0ae40ef

View File

@ -70,52 +70,32 @@ namespace osu.Game.Rulesets.Objects.Legacy
} }
else if (type.HasFlag(LegacyHitObjectType.Slider)) else if (type.HasFlag(LegacyHitObjectType.Slider))
{ {
PathType pathType = PathType.Catmull;
double? length = null; double? length = null;
string[] pointSplit = split[5].Split('|'); string[] pointSplit = split[5].Split('|');
int pointCount = 1; var controlPoints = new List<Memory<PathControlPoint>>();
int startIndex = 0;
int endIndex = 0;
bool first = true;
foreach (var t in pointSplit) while (++endIndex < pointSplit.Length)
{ {
if (t.Length > 1) // Keep incrementing endIndex while it's not the start of a new segment (indicated by having a type descriptor of length 1).
pointCount++; if (pointSplit[endIndex].Length > 1)
}
var points = new Vector2[pointCount];
int pointIndex = 1;
foreach (string t in pointSplit)
{
if (t.Length == 1)
{
switch (t)
{
case @"C":
pathType = PathType.Catmull;
break;
case @"B":
pathType = PathType.Bezier;
break;
case @"L":
pathType = PathType.Linear;
break;
case @"P":
pathType = PathType.PerfectCurve;
break;
}
continue; continue;
// Multi-segmented sliders DON'T contain the end point as part of the current segment as it's assumed to be the start of the next segment.
// The start of the next segment is the index after the type descriptor.
string endPoint = endIndex < pointSplit.Length - 1 ? pointSplit[endIndex + 1] : null;
controlPoints.Add(convertControlPoints(pointSplit.AsSpan().Slice(startIndex, endIndex - startIndex), endPoint, first, pos));
startIndex = endIndex;
first = false;
} }
string[] temp = t.Split(':'); if (endIndex > startIndex)
points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos; controlPoints.Add(convertControlPoints(pointSplit.AsSpan().Slice(startIndex, endIndex - startIndex), null, first, pos));
}
int repeatCount = Parsing.ParseInt(split[6]); int repeatCount = Parsing.ParseInt(split[6]);
@ -183,7 +163,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
for (int i = 0; i < nodes; i++) for (int i = 0; i < nodes; i++)
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
result = CreateSlider(pos, combo, comboOffset, convertControlPoints(points, pathType), length, repeatCount, nodeSamples); result = CreateSlider(pos, combo, comboOffset, mergeControlPoints(controlPoints), length, repeatCount, nodeSamples);
} }
else if (type.HasFlag(LegacyHitObjectType.Spinner)) else if (type.HasFlag(LegacyHitObjectType.Spinner))
{ {
@ -252,8 +232,56 @@ namespace osu.Game.Rulesets.Objects.Legacy
bankInfo.Filename = split.Length > 4 ? split[4] : null; bankInfo.Filename = split.Length > 4 ? split[4] : null;
} }
private PathControlPoint[] convertControlPoints(Vector2[] vertices, PathType type) private PathType convertPathType(string input)
{ {
switch (input[0])
{
default:
case 'C':
return PathType.Catmull;
case 'B':
return PathType.Bezier;
case 'L':
return PathType.Linear;
case 'P':
return PathType.PerfectCurve;
}
}
/// <summary>
/// Converts a given point list into a set of <see cref="PathControlPoint"/>s.
/// </summary>
/// <param name="pointSpan">The point list.</param>
/// <param name="endPoint">Any extra endpoint to consider as part of the points. This will NOT be returned.</param>
/// <param name="first">Whether this is the first point list in the set. If <c>true</c> the returned set will contain a zero point.</param>
/// <param name="offset">The positional offset to apply to the control points.</param>
/// <returns>The set of points contained by <paramref name="pointSpan"/>, prepended by an extra zero point if <paramref name="first"/> is <c>true</c>.</returns>
private Memory<PathControlPoint> convertControlPoints(ReadOnlySpan<string> pointSpan, string endPoint, bool first, Vector2 offset)
{
PathType type = convertPathType(pointSpan[0]);
int readOffset = first ? 1 : 0; // First control point is zero for the first segment.
int readablePoints = pointSpan.Length - 1; // Total points readable from the base point span.
int endPointLength = endPoint != null ? 1 : 0; // Extra length if an endpoint is given that lies outside the base point span.
var vertices = new PathControlPoint[readOffset + readablePoints + endPointLength];
// Fill any non-read points.
for (int i = 0; i < readOffset; i++)
vertices[i] = new PathControlPoint();
// Parse into control points.
for (int i = 1; i < pointSpan.Length; i++)
readPoint(pointSpan[i], offset, out vertices[readOffset + i - 1]);
// If an endpoint is given, add it now.
if (endPoint != null)
readPoint(endPoint, offset, out vertices[^1]);
// Edge-case rules.
if (type == PathType.PerfectCurve) if (type == PathType.PerfectCurve)
{ {
if (vertices.Length != 3) if (vertices.Length != 3)
@ -265,29 +293,48 @@ namespace osu.Game.Rulesets.Objects.Legacy
} }
} }
var points = new List<PathControlPoint>(vertices.Length) // Set a definite type for the first control point.
{ vertices[0].Type.Value = type;
new PathControlPoint
{
Position = { Value = vertices[0] },
Type = { Value = type }
}
};
// A path can have multiple segments of the same type if there are two sequential control points with the same position.
for (int i = 1; i < vertices.Length; i++) for (int i = 1; i < vertices.Length; i++)
{ {
if (vertices[i] == vertices[i - 1]) if (vertices[i] == vertices[i - 1])
vertices[i].Type.Value = type;
}
return vertices.AsMemory().Slice(0, vertices.Length - endPointLength);
static void readPoint(string value, Vector2 startPos, out PathControlPoint point)
{ {
points[^1].Type.Value = type; string[] vertexSplit = value.Split(':');
continue;
Vector2 pos = new Vector2((int)Parsing.ParseDouble(vertexSplit[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(vertexSplit[1], Parsing.MAX_COORDINATE_VALUE)) - startPos;
point = new PathControlPoint { Position = { Value = pos } };
} }
points.Add(new PathControlPoint { Position = { Value = vertices[i] } }); static bool isLinear(PathControlPoint[] p) => Precision.AlmostEquals(0, (p[1].Position.Value.Y - p[0].Position.Value.Y) * (p[2].Position.Value.X - p[0].Position.Value.X)
- (p[1].Position.Value.X - p[0].Position.Value.X) * (p[2].Position.Value.Y - p[0].Position.Value.Y));
} }
return points.ToArray(); private PathControlPoint[] mergeControlPoints(List<Memory<PathControlPoint>> controlPointList)
{
int totalCount = 0;
static bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); foreach (var arr in controlPointList)
totalCount += arr.Length;
var mergedArray = new PathControlPoint[totalCount];
var mergedArrayMemory = mergedArray.AsMemory();
int copyIndex = 0;
foreach (var arr in controlPointList)
{
arr.CopyTo(mergedArrayMemory.Slice(copyIndex));
copyIndex += arr.Length;
}
return mergedArray;
} }
/// <summary> /// <summary>