Merge pull request #24527 from Pasi4K5/fix-slider-reversing

Fix sliders being reversed incorrectly in the editor
This commit is contained in:
Bartłomiej Dach 2023-08-21 17:12:00 +02:00 committed by GitHub
commit aa5680a8aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 158 additions and 0 deletions

View File

@ -0,0 +1,110 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Tests.Beatmaps;
using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Tests.Editor
{
public partial class TestSceneSliderReversal : TestSceneOsuEditor
{
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(Ruleset.Value, false);
private readonly PathControlPoint[][] paths =
{
createPathSegment(
PathType.PerfectCurve,
new Vector2(200, -50),
new Vector2(250, 0)
),
createPathSegment(
PathType.Linear,
new Vector2(100, 0),
new Vector2(100, 100)
)
};
private static PathControlPoint[] createPathSegment(PathType type, params Vector2[] positions)
{
return positions.Select(p => new PathControlPoint
{
Position = p
}).Prepend(new PathControlPoint
{
Type = type
}).ToArray();
}
private Slider selectedSlider => (Slider)EditorBeatmap.SelectedHitObjects[0];
[TestCase(0, 250)]
[TestCase(0, 200)]
[TestCase(1, 120)]
[TestCase(1, 80)]
public void TestSliderReversal(int pathIndex, double length)
{
var controlPoints = paths[pathIndex];
Vector2 oldStartPos = default;
Vector2 oldEndPos = default;
double oldDistance = default;
var oldControlPointTypes = controlPoints.Select(p => p.Type);
AddStep("Add slider", () =>
{
var slider = new Slider
{
Position = new Vector2(OsuPlayfield.BASE_SIZE.X / 2, OsuPlayfield.BASE_SIZE.Y / 2),
Path = new SliderPath(controlPoints)
{
ExpectedDistance = { Value = length }
}
};
EditorBeatmap.Add(slider);
oldStartPos = slider.Position;
oldEndPos = slider.EndPosition;
oldDistance = slider.Path.Distance;
});
AddStep("Select slider", () =>
{
var slider = (Slider)EditorBeatmap.HitObjects[0];
EditorBeatmap.SelectedHitObjects.Add(slider);
});
AddStep("Reverse slider", () =>
{
InputManager.PressKey(Key.LControl);
InputManager.Key(Key.G);
InputManager.ReleaseKey(Key.LControl);
});
AddAssert("Slider has correct length", () =>
Precision.AlmostEquals(selectedSlider.Path.Distance, oldDistance));
AddAssert("Slider has correct start position", () =>
Vector2.Distance(selectedSlider.Position, oldEndPos) < 1);
AddAssert("Slider has correct end position", () =>
Vector2.Distance(selectedSlider.EndPosition, oldStartPos) < 1);
AddAssert("Control points have correct types", () =>
{
var newControlPointTypes = selectedSlider.Path.ControlPoints.Select(p => p.Type).ToArray();
return oldControlPointTypes.Take(newControlPointTypes.Length).SequenceEqual(newControlPointTypes);
});
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Types;
@ -25,6 +26,53 @@ namespace osu.Game.Rulesets.Objects
/// <param name="sliderPath">The <see cref="SliderPath"/>.</param>
/// <param name="positionalOffset">The positional offset of the resulting path. It should be added to the start position of this path.</param>
public static void Reverse(this SliderPath sliderPath, out Vector2 positionalOffset)
{
var controlPoints = sliderPath.ControlPoints;
var inheritedLinearPoints = controlPoints.Where(p => sliderPath.PointsInSegment(p)[0].Type == PathType.Linear && p.Type is null).ToList();
// Inherited points after a linear point, as well as the first control point if it inherited,
// should be treated as linear points, so their types are temporarily changed to linear.
inheritedLinearPoints.ForEach(p => p.Type = PathType.Linear);
double[] segmentEnds = sliderPath.GetSegmentEnds().ToArray();
// Remove segments after the end of the slider.
for (int numSegmentsToRemove = segmentEnds.Count(se => se >= 1) - 1; numSegmentsToRemove > 0 && controlPoints.Count > 0;)
{
if (controlPoints.Last().Type is not null)
{
numSegmentsToRemove--;
segmentEnds = segmentEnds[..^1];
}
controlPoints.RemoveAt(controlPoints.Count - 1);
}
// Restore original control point types.
inheritedLinearPoints.ForEach(p => p.Type = null);
// Recalculate middle perfect curve control points at the end of the slider path.
if (controlPoints.Count >= 3 && controlPoints[^3].Type == PathType.PerfectCurve && controlPoints[^2].Type is null && segmentEnds.Any())
{
double lastSegmentStart = segmentEnds.Length > 1 ? segmentEnds[^2] : 0;
double lastSegmentEnd = segmentEnds[^1];
var circleArcPath = new List<Vector2>();
sliderPath.GetPathToProgress(circleArcPath, lastSegmentStart / lastSegmentEnd, 1);
controlPoints[^2].Position = circleArcPath[circleArcPath.Count / 2];
}
sliderPath.reverseControlPoints(out positionalOffset);
}
/// <summary>
/// Reverses the order of the provided <see cref="SliderPath"/>'s <see cref="PathControlPoint"/>s.
/// </summary>
/// <param name="sliderPath">The <see cref="SliderPath"/>.</param>
/// <param name="positionalOffset">The positional offset of the resulting path. It should be added to the start position of this path.</param>
private static void reverseControlPoints(this SliderPath sliderPath, out Vector2 positionalOffset)
{
var points = sliderPath.ControlPoints.ToArray();
positionalOffset = sliderPath.PositionAt(1);