Merge pull request #12309 from smoogipoo/fix-lazer-encoder-fp-error

Fix floating point error in slider path encoding
This commit is contained in:
Dean Herbert 2021-04-06 15:19:09 +09:00 committed by GitHub
commit 35af44ab98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 48 additions and 5 deletions

View File

@ -18,10 +18,14 @@ using osu.Game.IO;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Taiko;
using osu.Game.Skinning;
using osu.Game.Tests.Resources;
using osuTK;
namespace osu.Game.Tests.Beatmaps.Formats
{
@ -45,6 +49,33 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
}
[Test]
public void TestEncodeMultiSegmentSliderWithFloatingPointError()
{
var beatmap = new Beatmap
{
HitObjects =
{
new Slider
{
Position = new Vector2(0.6f),
Path = new SliderPath(new[]
{
new PathControlPoint(Vector2.Zero, PathType.Bezier),
new PathControlPoint(new Vector2(0.5f)),
new PathControlPoint(new Vector2(0.51f)), // This is actually on the same position as the previous one in legacy beatmaps (truncated to int).
new PathControlPoint(new Vector2(1f), PathType.Bezier),
new PathControlPoint(new Vector2(2f))
})
},
}
};
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy((beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty))), string.Empty);
var decodedSlider = (Slider)decodedAfterEncode.beatmap.HitObjects[0];
Assert.That(decodedSlider.Path.ControlPoints.Count, Is.EqualTo(5));
}
private bool areComboColoursEqual(IHasComboColours a, IHasComboColours b)
{
// equal to null, no need to SequenceEqual

View File

@ -329,13 +329,25 @@ namespace osu.Game.Beatmaps.Formats
if (point.Type.Value != null)
{
// We've reached a new segment!
// We've reached a new (explicit) segment!
// To preserve compatibility with osu-stable as much as possible, segments with the same type are converted to use implicit segments by duplicating the control point.
// One exception to this is when the last control point of the last segment was itself a duplicate, which can't be supported by osu-stable.
bool lastPointWasDuplicate = i > 1 && pathData.Path.ControlPoints[i - 1].Position.Value == pathData.Path.ControlPoints[i - 2].Position.Value;
// 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;
if (lastPointWasDuplicate || point.Type.Value != lastType)
// 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.
// 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.
Vector2 p1 = position + pathData.Path.ControlPoints[i - 1].Position.Value;
Vector2 p2 = position + pathData.Path.ControlPoints[i - 2].Position.Value;
if ((int)p1.X == (int)p2.X && (int)p1.Y == (int)p2.Y)
needsExplicitSegment = true;
}
if (needsExplicitSegment)
{
switch (point.Type.Value)
{