From d81f270e21f81f5ff91f5a5d27a04e30687d2543 Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Mon, 5 Apr 2021 18:01:16 +0200 Subject: [PATCH 1/2] Always encode perfect curves as explicit segments --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 5eb5fcdfa0..0bb1aa873f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -333,9 +333,10 @@ namespace osu.Game.Beatmaps.Formats // 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. - bool needsExplicitSegment = point.Type.Value != lastType; + // One exception are consecutive perfect curves, which aren't supported in osu-stable + bool needsExplicitSegment = point.Type.Value != lastType || point.Type.Value == PathType.PerfectCurve; - // One 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. + // 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) { From dd902441b098983802cb1bbda3fae0ad8c86fa5f Mon Sep 17 00:00:00 2001 From: Leon Gebler Date: Mon, 5 Apr 2021 17:21:45 +0200 Subject: [PATCH 2/2] Add tests for consecutive perfect-curve segments --- .../Formats/LegacyBeatmapDecoderTest.cs | 30 +++++++++++++++++++ .../Resources/multi-segment-slider.osu | 7 +++++ .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index fb18be3ae1..0f82492e51 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -740,6 +740,36 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(fifth.ControlPoints[5].Type.Value, Is.EqualTo(PathType.Bezier)); Assert.That(fifth.ControlPoints[6].Position.Value, Is.EqualTo(new Vector2(5, 5))); Assert.That(fifth.ControlPoints[6].Type.Value, Is.EqualTo(null)); + + // Implicit perfect-curve multi-segment(Should convert to bezier to match stable) + var sixth = ((IHasPath)decoded.HitObjects[5]).Path; + + Assert.That(sixth.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); + Assert.That(sixth.ControlPoints[0].Type.Value == PathType.Bezier); + Assert.That(sixth.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(75, 145))); + Assert.That(sixth.ControlPoints[1].Type.Value == null); + Assert.That(sixth.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(170, 75))); + + Assert.That(sixth.ControlPoints[2].Type.Value == PathType.Bezier); + Assert.That(sixth.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(300, 145))); + Assert.That(sixth.ControlPoints[3].Type.Value == null); + Assert.That(sixth.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(410, 20))); + Assert.That(sixth.ControlPoints[4].Type.Value == null); + + // Explicit perfect-curve multi-segment(Should not convert to bezier) + var seventh = ((IHasPath)decoded.HitObjects[6]).Path; + + Assert.That(seventh.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero)); + Assert.That(seventh.ControlPoints[0].Type.Value == PathType.PerfectCurve); + Assert.That(seventh.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(75, 145))); + Assert.That(seventh.ControlPoints[1].Type.Value == null); + Assert.That(seventh.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(170, 75))); + + Assert.That(seventh.ControlPoints[2].Type.Value == PathType.PerfectCurve); + Assert.That(seventh.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(300, 145))); + Assert.That(seventh.ControlPoints[3].Type.Value == null); + Assert.That(seventh.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(410, 20))); + Assert.That(seventh.ControlPoints[4].Type.Value == null); } } } diff --git a/osu.Game.Tests/Resources/multi-segment-slider.osu b/osu.Game.Tests/Resources/multi-segment-slider.osu index cc86710067..135132e35c 100644 --- a/osu.Game.Tests/Resources/multi-segment-slider.osu +++ b/osu.Game.Tests/Resources/multi-segment-slider.osu @@ -15,3 +15,10 @@ osu file format v128 // Last control point in segment duplicated 0,0,5000,2,0,B|1:1|2:2|3:3|3:3|B|4:4|5:5,2,200 + +// Implicit perfect-curve multi-segment (Should convert to bezier to match stable) +0,0,6000,2,0,P|75:145|170:75|170:75|300:145|410:20,1,475,0:0:0:0: + +// Explicit perfect-curve multi-segment (Should not convert to bezier) +0,0,7000,2,0,P|75:145|P|170:75|300:145|410:20,1,650,0:0:0:0: + diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 0bb1aa873f..da44b96ed3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -333,7 +333,7 @@ namespace osu.Game.Beatmaps.Formats // 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 + // 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.Value != lastType || point.Type.Value == PathType.PerfectCurve; // 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.