From 2a88409dfe65fd5e48c52bbfdb0397e8d8b4f575 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Nov 2022 15:00:23 +0900 Subject: [PATCH 1/4] Fix time snap of sliders not matching when SV is not 1.0x This regressed with https://github.com/ppy/osu/pull/20850 because the function was used in other places which expect it to factor slider velocity into the equation. Rather than reverting, I've added a new argument, as based on the method naming alone it was hard to discern whether SV should actually be considered. The reason for the change in #20850 was to avoid the SV coming in from a reference object which may not have a correct SV in the first place. In such cases, passing `false` to the function will give the expected behaviour. --- osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs | 2 +- osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs | 6 +++--- osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs | 3 ++- .../Screens/Edit/Compose/Components/DistanceSnapGrid.cs | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index 53b6db227774..01a49c7dea79 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -193,7 +193,7 @@ private class SnapProvider : IDistanceSnapProvider IBindable IDistanceSnapProvider.DistanceSpacingMultiplier => DistanceSpacingMultiplier; - public float GetBeatSnapDistanceAt(HitObject referenceObject) => beat_snap_distance; + public float GetBeatSnapDistanceAt(HitObject referenceObject, bool useReferenceSliderVelocity = true) => beat_snap_distance; public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration; diff --git a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs index ca7ca798133f..840ed7682a73 100644 --- a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs @@ -259,9 +259,9 @@ protected virtual bool AdjustDistanceSpacing(GlobalAction action, float amount) return true; } - public virtual float GetBeatSnapDistanceAt(HitObject referenceObject) + public virtual float GetBeatSnapDistanceAt(HitObject referenceObject, bool useReferenceSliderVelocity = true) { - return (float)(100 * EditorBeatmap.Difficulty.SliderMultiplier * 1 / BeatSnapProvider.BeatDivisor); + return (float)(100 * (useReferenceSliderVelocity ? referenceObject.DifficultyControlPoint.SliderVelocity : 1) * EditorBeatmap.Difficulty.SliderMultiplier * 1 / BeatSnapProvider.BeatDivisor); } public virtual float DurationToDistance(HitObject referenceObject, double duration) @@ -273,7 +273,7 @@ public virtual float DurationToDistance(HitObject referenceObject, double durati public virtual double DistanceToDuration(HitObject referenceObject, float distance) { double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime); - return distance / GetBeatSnapDistanceAt(referenceObject) * beatLength; + return distance / GetBeatSnapDistanceAt(referenceObject) * beatLength * referenceObject.DifficultyControlPoint.SliderVelocity; } public virtual double FindSnappedDuration(HitObject referenceObject, float distance) diff --git a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs index 5ad1cc78ff11..6fbd994e23a2 100644 --- a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs @@ -27,8 +27,9 @@ public interface IDistanceSnapProvider : IPositionSnapProvider /// Retrieves the distance between two points within a timing point that are one beat length apart. /// /// An object to be used as a reference point for this operation. + /// Whether the 's slider velocity should be factored into the returned distance. /// The distance between two points residing in the timing point that are one beat length apart. - float GetBeatSnapDistanceAt(HitObject referenceObject); + float GetBeatSnapDistanceAt(HitObject referenceObject, bool useReferenceSliderVelocity = true); /// /// Converts a duration to a distance without applying any snapping. diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 69c7fc27750f..c179e7f0c287 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -97,7 +97,7 @@ protected override void LoadComplete() private void updateSpacing() { float distanceSpacingMultiplier = (float)DistanceSpacingMultiplier.Value; - float beatSnapDistance = SnapProvider.GetBeatSnapDistanceAt(ReferenceObject); + float beatSnapDistance = SnapProvider.GetBeatSnapDistanceAt(ReferenceObject, false); DistanceBetweenTicks = beatSnapDistance * distanceSpacingMultiplier; From e8b791429569d3f9513265ae7ae4f23c2754aba7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Nov 2022 15:13:05 +0900 Subject: [PATCH 2/4] Add test coverage of new parameter --- ...tSceneHitObjectComposerDistanceSnapping.cs | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index a98f931e7a3a..6fb21e38b08e 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -66,7 +66,7 @@ public void TestSliderMultiplier(float multiplier) { AddStep($"set slider multiplier = {multiplier}", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = multiplier); - assertSnapDistance(100 * multiplier); + assertSnapDistance(100 * multiplier, null, true); } [TestCase(1)] @@ -79,7 +79,20 @@ public void TestSpeedMultiplierDoesNotChangeDistanceSnap(float multiplier) { SliderVelocity = multiplier } - }); + }, false); + } + + [TestCase(1)] + [TestCase(2)] + public void TestSpeedMultiplierDoesChangeDistanceSnap(float multiplier) + { + assertSnapDistance(100 * multiplier, new HitObject + { + DifficultyControlPoint = new DifficultyControlPoint + { + SliderVelocity = multiplier + } + }, true); } [TestCase(1)] @@ -88,7 +101,7 @@ public void TestBeatDivisor(int divisor) { AddStep($"set divisor = {divisor}", () => BeatDivisor.Value = divisor); - assertSnapDistance(100f / divisor); + assertSnapDistance(100f / divisor, null, true); } [Test] @@ -197,8 +210,8 @@ public void GetSnappedDistanceFromDistance() assertSnappedDistance(400, 400); } - private void assertSnapDistance(float expectedDistance, HitObject? hitObject = null) - => AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(hitObject ?? new HitObject()), () => Is.EqualTo(expectedDistance)); + private void assertSnapDistance(float expectedDistance, HitObject? refereneObject, bool includeSliderVelocity) + => AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(refereneObject ?? new HitObject(), includeSliderVelocity), () => Is.EqualTo(expectedDistance)); private void assertDurationToDistance(double duration, float expectedDistance) => AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(new HitObject(), duration) == expectedDistance); From d807d9d82299c85e13405472394061668e1f1698 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Nov 2022 15:21:17 +0900 Subject: [PATCH 3/4] Add failing test covering snap calculations with SV applied --- ...tSceneHitObjectComposerDistanceSnapping.cs | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 6fb21e38b08e..f759ed5dcfeb 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -104,6 +105,28 @@ public void TestBeatDivisor(int divisor) assertSnapDistance(100f / divisor, null, true); } + /// + /// The basic distance-duration functions should always include slider velocity of the reference object. + /// + [Test] + public void TestConversionsWithSliderVelocity() + { + const float base_distance = 100; + const float slider_velocity = 1.2f; + + var referenceObject = new HitObject + { + DifficultyControlPoint = new DifficultyControlPoint + { + SliderVelocity = slider_velocity + } + }; + + assertSnapDistance(base_distance * slider_velocity, referenceObject, true); + assertSnappedDistance(base_distance * slider_velocity + 10, base_distance * slider_velocity, referenceObject); + assertSnappedDuration(base_distance * slider_velocity + 10, 1000, referenceObject); + } + [Test] public void TestConvertDurationToDistance() { @@ -210,20 +233,20 @@ public void GetSnappedDistanceFromDistance() assertSnappedDistance(400, 400); } - private void assertSnapDistance(float expectedDistance, HitObject? refereneObject, bool includeSliderVelocity) - => AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(refereneObject ?? new HitObject(), includeSliderVelocity), () => Is.EqualTo(expectedDistance)); + private void assertSnapDistance(float expectedDistance, HitObject? referenceObject, bool includeSliderVelocity) + => AddAssert($"distance is {expectedDistance}", () => composer.GetBeatSnapDistanceAt(referenceObject ?? new HitObject(), includeSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON)); - private void assertDurationToDistance(double duration, float expectedDistance) - => AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(new HitObject(), duration) == expectedDistance); + private void assertDurationToDistance(double duration, float expectedDistance, HitObject? referenceObject = null) + => AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DurationToDistance(referenceObject ?? new HitObject(), duration), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON)); - private void assertDistanceToDuration(float distance, double expectedDuration) - => AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceToDuration(new HitObject(), distance) == expectedDuration); + private void assertDistanceToDuration(float distance, double expectedDuration, HitObject? referenceObject = null) + => AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceToDuration(referenceObject ?? new HitObject(), distance), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON)); - private void assertSnappedDuration(float distance, double expectedDuration) - => AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.FindSnappedDuration(new HitObject(), distance) == expectedDuration); + private void assertSnappedDuration(float distance, double expectedDuration, HitObject? referenceObject = null) + => AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.FindSnappedDuration(referenceObject ?? new HitObject(), distance), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON)); - private void assertSnappedDistance(float distance, float expectedDistance) - => AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.FindSnappedDistance(new HitObject(), distance) == expectedDistance); + private void assertSnappedDistance(float distance, float expectedDistance, HitObject? referenceObject = null) + => AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.FindSnappedDistance(referenceObject ?? new HitObject(), distance), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON)); private class TestHitObjectComposer : OsuHitObjectComposer { From 29bc653d24caaf65038ee39c75b7dab1bfc5307b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Nov 2022 17:14:30 +0900 Subject: [PATCH 4/4] Remove incorrect double multiplication and add missing test coverage --- .../Editing/TestSceneHitObjectComposerDistanceSnapping.cs | 3 +++ osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index f759ed5dcfeb..495a2211594f 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -125,6 +125,9 @@ public void TestConversionsWithSliderVelocity() assertSnapDistance(base_distance * slider_velocity, referenceObject, true); assertSnappedDistance(base_distance * slider_velocity + 10, base_distance * slider_velocity, referenceObject); assertSnappedDuration(base_distance * slider_velocity + 10, 1000, referenceObject); + + assertDistanceToDuration(base_distance * slider_velocity, 1000, referenceObject); + assertDurationToDistance(1000, base_distance * slider_velocity, referenceObject); } [Test] diff --git a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs index 840ed7682a73..b0a2694a0a27 100644 --- a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs @@ -273,7 +273,7 @@ public virtual float DurationToDistance(HitObject referenceObject, double durati public virtual double DistanceToDuration(HitObject referenceObject, float distance) { double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime); - return distance / GetBeatSnapDistanceAt(referenceObject) * beatLength * referenceObject.DifficultyControlPoint.SliderVelocity; + return distance / GetBeatSnapDistanceAt(referenceObject) * beatLength; } public virtual double FindSnappedDuration(HitObject referenceObject, float distance)