mirror of
https://github.com/ppy/osu.git
synced 2025-02-12 14:43:03 +08:00
Merge pull request #31734 from bdach/simplify-slider-encoding-logic
Fix multi-segment-type sliders getting mangled on legacy export
This commit is contained in:
commit
1e1fb5476e
@ -447,60 +447,31 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
private void addPathData(TextWriter writer, IHasPath pathData, Vector2 position)
|
private void addPathData(TextWriter writer, IHasPath pathData, Vector2 position)
|
||||||
{
|
{
|
||||||
PathType? lastType = null;
|
|
||||||
|
|
||||||
for (int i = 0; i < pathData.Path.ControlPoints.Count; i++)
|
for (int i = 0; i < pathData.Path.ControlPoints.Count; i++)
|
||||||
{
|
{
|
||||||
PathControlPoint point = pathData.Path.ControlPoints[i];
|
PathControlPoint point = pathData.Path.ControlPoints[i];
|
||||||
|
|
||||||
|
// Note that lazer's encoding format supports specifying multiple curve types for a slider path, which is not supported by stable.
|
||||||
|
// Backwards compatibility with stable is handled by `LegacyBeatmapExporter` and `BezierConverter.ConvertToModernBezier()`.
|
||||||
if (point.Type != null)
|
if (point.Type != null)
|
||||||
{
|
{
|
||||||
// We've reached a new (explicit) segment!
|
switch (point.Type?.Type)
|
||||||
|
|
||||||
// Explicit segments have a new format in which the type is injected into the middle of the control point string.
|
|
||||||
// To preserve compatibility with osu-stable as much as possible, explicit segments with the same type are converted to use implicit segments by duplicating the control point.
|
|
||||||
// One exception are consecutive perfect curves, which aren't supported in osu!stable and can lead to decoding issues if encoded as implicit segments
|
|
||||||
bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PERFECT_CURVE || i == pathData.Path.ControlPoints.Count - 1;
|
|
||||||
|
|
||||||
// Another exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable.
|
|
||||||
// Lazer does not add implicit segments for the last two control points of _any_ explicit segment, so an explicit segment is forced in order to maintain consistency with the decoder.
|
|
||||||
if (i > 1)
|
|
||||||
{
|
{
|
||||||
// We need to use the absolute control point position to determine equality, otherwise floating point issues may arise.
|
case SplineType.BSpline:
|
||||||
Vector2 p1 = position + pathData.Path.ControlPoints[i - 1].Position;
|
writer.Write(point.Type.Value.Degree > 0 ? $"B{point.Type.Value.Degree}|" : "B|");
|
||||||
Vector2 p2 = position + pathData.Path.ControlPoints[i - 2].Position;
|
break;
|
||||||
|
|
||||||
if ((int)p1.X == (int)p2.X && (int)p1.Y == (int)p2.Y)
|
case SplineType.Catmull:
|
||||||
needsExplicitSegment = true;
|
writer.Write("C|");
|
||||||
}
|
break;
|
||||||
|
|
||||||
if (needsExplicitSegment)
|
case SplineType.PerfectCurve:
|
||||||
{
|
writer.Write("P|");
|
||||||
switch (point.Type?.Type)
|
break;
|
||||||
{
|
|
||||||
case SplineType.BSpline:
|
|
||||||
writer.Write(point.Type.Value.Degree > 0 ? $"B{point.Type.Value.Degree}|" : "B|");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SplineType.Catmull:
|
case SplineType.Linear:
|
||||||
writer.Write("C|");
|
writer.Write("L|");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SplineType.PerfectCurve:
|
|
||||||
writer.Write("P|");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SplineType.Linear:
|
|
||||||
writer.Write("L|");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastType = point.Type;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// New segment with the same type - duplicate the control point
|
|
||||||
writer.Write(FormattableString.Invariant($"{position.X + point.Position.X}:{position.Y + point.Position.Y}|"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,18 +120,30 @@ namespace osu.Game.Database
|
|||||||
if (BezierConverter.CountSegments(hasPath.Path.ControlPoints) <= 1
|
if (BezierConverter.CountSegments(hasPath.Path.ControlPoints) <= 1
|
||||||
&& hasPath.Path.ControlPoints[0].Type!.Value.Degree == null) continue;
|
&& hasPath.Path.ControlPoints[0].Type!.Value.Degree == null) continue;
|
||||||
|
|
||||||
var newControlPoints = BezierConverter.ConvertToModernBezier(hasPath.Path.ControlPoints);
|
var convertedToBezier = BezierConverter.ConvertToModernBezier(hasPath.Path.ControlPoints);
|
||||||
|
|
||||||
// Truncate control points to integer positions
|
|
||||||
foreach (var pathControlPoint in newControlPoints)
|
|
||||||
{
|
|
||||||
pathControlPoint.Position = new Vector2(
|
|
||||||
(float)Math.Floor(pathControlPoint.Position.X),
|
|
||||||
(float)Math.Floor(pathControlPoint.Position.Y));
|
|
||||||
}
|
|
||||||
|
|
||||||
hasPath.Path.ControlPoints.Clear();
|
hasPath.Path.ControlPoints.Clear();
|
||||||
hasPath.Path.ControlPoints.AddRange(newControlPoints);
|
|
||||||
|
for (int i = 0; i < convertedToBezier.Count; i++)
|
||||||
|
{
|
||||||
|
var convertedPoint = convertedToBezier[i];
|
||||||
|
|
||||||
|
// Truncate control points to integer positions
|
||||||
|
var position = new Vector2(
|
||||||
|
(float)Math.Floor(convertedPoint.Position.X),
|
||||||
|
(float)Math.Floor(convertedPoint.Position.Y));
|
||||||
|
|
||||||
|
// stable only supports a single curve type specification per slider.
|
||||||
|
// we exploit the fact that the converted-to-Bézier path only has Bézier segments,
|
||||||
|
// and thus we specify the Bézier curve type once ever at the start of the slider.
|
||||||
|
hasPath.Path.ControlPoints.Add(new PathControlPoint(position, i == 0 ? PathType.BEZIER : null));
|
||||||
|
|
||||||
|
// however, the Bézier path as output by the converter has multiple segments.
|
||||||
|
// `LegacyBeatmapEncoder` will attempt to encode this by emitting per-control-point curve type specs which don't do anything for stable.
|
||||||
|
// instead, stable expects control points that start a segment to be present in the path twice in succession.
|
||||||
|
if (convertedPoint.Type == PathType.BEZIER && i > 0)
|
||||||
|
hasPath.Path.ControlPoints.Add(new PathControlPoint(position));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode to legacy format
|
// Encode to legacy format
|
||||||
|
Loading…
Reference in New Issue
Block a user