From a1185df2ebb833c0cfb9a4a93987a2a97e547453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 22 Jan 2025 14:33:14 +0100 Subject: [PATCH 1/4] Refactor `IDistanceSnapProvider` to accept slider velocity objects as a reference Method signatures are also changed to be a lot more explicit as to what inputs they expect. --- .../Edit/CatchDistanceSnapProvider.cs | 2 +- .../Components/PathControlPointVisualiser.cs | 2 +- .../Sliders/SliderPlacementBlueprint.cs | 2 +- .../Sliders/SliderSelectionBlueprint.cs | 4 +- .../Edit/OsuDistanceSnapGrid.cs | 5 +- .../Edit/OsuDistanceSnapProvider.cs | 2 +- .../Edit/OsuHitObjectComposer.cs | 17 ++-- ...tSceneHitObjectComposerDistanceSnapping.cs | 31 +++---- .../Editing/TestSceneDistanceSnapGrid.cs | 14 +-- .../Edit/ComposerDistanceSnapProvider.cs | 46 +++------ .../Rulesets/Edit/IDistanceSnapProvider.cs | 93 ++++++++++++------- .../Rulesets/Objects/SliderPathExtensions.cs | 4 +- .../Components/CircularDistanceSnapGrid.cs | 30 +++--- .../Compose/Components/DistanceSnapGrid.cs | 20 ++-- 14 files changed, 138 insertions(+), 134 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs index ae4025aa2f..420a0eb34f 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapProvider.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Edit // // The implementation below is probably correct but should be checked if/when exposed via controls. - float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime()); + float expectedDistance = DurationToDistance(after.StartTime - before.GetEndTime(), before.StartTime); float previousEndX = (before as JuiceStream)?.EndX ?? ((CatchHitObject)before).EffectiveX; float actualDistance = Math.Abs(previousEndX - ((CatchHitObject)after).EffectiveX); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index b9938209ae..bc3d27fd68 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -34,7 +34,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public partial class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler, IHasContextMenu - where T : OsuHitObject, IHasPath + where T : OsuHitObject, IHasPath, IHasSliderVelocity { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside the playfield. diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 21817045c4..a747d4fce8 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -434,7 +434,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (state == SliderPlacementState.Drawing) HitObject.Path.ExpectedDistance.Value = (float)HitObject.Path.CalculatedDistance; else - HitObject.Path.ExpectedDistance.Value = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)HitObject.Path.CalculatedDistance, DistanceSnapTarget.Start) ?? (float)HitObject.Path.CalculatedDistance; + HitObject.Path.ExpectedDistance.Value = distanceSnapProvider?.FindSnappedDistance((float)HitObject.Path.CalculatedDistance, HitObject.StartTime, HitObject) ?? (float)HitObject.Path.CalculatedDistance; bodyPiece.UpdateFrom(HitObject); headCirclePiece.UpdateFrom(HitObject.HeadCircle); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 740862c9fd..f7c25b43dd 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -274,9 +274,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } else { - double minDistance = distanceSnapProvider?.GetBeatSnapDistanceAt(HitObject, false) * oldVelocityMultiplier ?? 1; + double minDistance = distanceSnapProvider?.GetBeatSnapDistance() * oldVelocityMultiplier ?? 1; // Add a small amount to the proposed distance to make it easier to snap to the full length of the slider. - proposedDistance = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)proposedDistance + 1, DistanceSnapTarget.Start) ?? proposedDistance; + proposedDistance = distanceSnapProvider?.FindSnappedDistance((float)proposedDistance + 1, HitObject.StartTime, HitObject) ?? proposedDistance; proposedDistance = MathHelper.Clamp(proposedDistance, minDistance, HitObject.Path.CalculatedDistance); } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index 848c994974..3323acce15 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -5,6 +5,7 @@ using JetBrains.Annotations; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -12,8 +13,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuDistanceSnapGrid : CircularDistanceSnapGrid { - public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null) - : base(hitObject, hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime - 1) + public OsuDistanceSnapGrid(OsuHitObject hitObject, [CanBeNull] OsuHitObject nextHitObject = null, [CanBeNull] IHasSliderVelocity sliderVelocitySource = null) + : base(hitObject.StackedEndPosition, hitObject.GetEndTime(), nextHitObject?.StartTime - 1, sliderVelocitySource) { Masking = true; } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs index 4042cfa0e2..3c0889d027 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapProvider.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Edit { public override double ReadCurrentDistanceSnap(HitObject before, HitObject after) { - float expectedDistance = DurationToDistance(before, after.StartTime - before.GetEndTime()); + float expectedDistance = DurationToDistance(after.StartTime - before.GetEndTime(), before.StartTime); float actualDistance = Vector2.Distance(((OsuHitObject)before).EndPosition, ((OsuHitObject)after).Position); return actualDistance / expectedDistance; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 563d0b1e3e..60c37cd4a4 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -23,6 +23,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; 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.Rulesets.UI; @@ -406,22 +407,26 @@ namespace osu.Game.Rulesets.Osu.Edit { ArgumentOutOfRangeException.ThrowIfNegativeOrZero(targetOffset); - int sourceIndex = -1; + int positionSourceObjectIndex = -1; + IHasSliderVelocity? sliderVelocitySource = null; for (int i = 0; i < EditorBeatmap.HitObjects.Count; i++) { if (!sourceSelector(EditorBeatmap.HitObjects[i])) break; - sourceIndex = i; + positionSourceObjectIndex = i; + + if (EditorBeatmap.HitObjects[i] is IHasSliderVelocity hasSliderVelocity) + sliderVelocitySource = hasSliderVelocity; } - if (sourceIndex == -1) + if (positionSourceObjectIndex == -1) return null; - HitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex]; + HitObject sourceObject = EditorBeatmap.HitObjects[positionSourceObjectIndex]; - int targetIndex = sourceIndex + targetOffset; + int targetIndex = positionSourceObjectIndex + targetOffset; HitObject targetObject = null; // Keep advancing the target object while its start time falls before the end time of the source object @@ -442,7 +447,7 @@ namespace osu.Game.Rulesets.Osu.Edit if (sourceObject is Spinner) return null; - return new OsuDistanceSnapGrid((OsuHitObject)sourceObject, (OsuHitObject)targetObject); + return new OsuDistanceSnapGrid((OsuHitObject)sourceObject, (OsuHitObject)targetObject, sliderVelocitySource); } } } diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index 0f8583253b..af116ad334 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -12,6 +12,7 @@ using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Osu.Edit; @@ -67,17 +68,7 @@ namespace osu.Game.Tests.Editing { AddStep($"set slider multiplier = {multiplier}", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = multiplier); - assertSnapDistance(100 * multiplier, null, true); - } - - [TestCase(1)] - [TestCase(2)] - public void TestSpeedMultiplierDoesNotChangeDistanceSnap(float multiplier) - { - assertSnapDistance(100, new Slider - { - SliderVelocityMultiplier = multiplier - }, false); + assertSnapDistance(100 * multiplier); } [TestCase(1)] @@ -87,7 +78,7 @@ namespace osu.Game.Tests.Editing assertSnapDistance(100 * multiplier, new Slider { SliderVelocityMultiplier = multiplier - }, true); + }); } [TestCase(1)] @@ -96,7 +87,7 @@ namespace osu.Game.Tests.Editing { AddStep($"set divisor = {divisor}", () => BeatDivisor.Value = divisor); - assertSnapDistance(100f / divisor, null, true); + assertSnapDistance(100f / divisor); } /// @@ -114,7 +105,7 @@ namespace osu.Game.Tests.Editing }; AddStep("add to beatmap", () => composer.EditorBeatmap.Add(referenceObject)); - assertSnapDistance(base_distance * slider_velocity, referenceObject, true); + assertSnapDistance(base_distance * slider_velocity, referenceObject); assertSnappedDistance(base_distance * slider_velocity + 10, base_distance * slider_velocity, referenceObject); assertSnappedDuration(base_distance * slider_velocity + 10, 1000, referenceObject); @@ -289,20 +280,20 @@ namespace osu.Game.Tests.Editing AddUntilStep("use current snap not available", () => getCurrentSnapButton().Enabled.Value, () => Is.False); } - private void assertSnapDistance(float expectedDistance, HitObject? referenceObject, bool includeSliderVelocity) - => AddAssert($"distance is {expectedDistance}", () => composer.DistanceSnapProvider.GetBeatSnapDistanceAt(referenceObject ?? new HitObject(), includeSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON)); + private void assertSnapDistance(float expectedDistance, IHasSliderVelocity? hasSliderVelocity = null) + => AddAssert($"distance is {expectedDistance}", () => composer.DistanceSnapProvider.GetBeatSnapDistance(hasSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON)); private void assertDurationToDistance(double duration, float expectedDistance, HitObject? referenceObject = null) - => AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DistanceSnapProvider.DurationToDistance(referenceObject ?? new HitObject(), duration), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON)); + => AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DistanceSnapProvider.DurationToDistance(duration, referenceObject?.StartTime ?? 0, referenceObject as IHasSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON)); private void assertDistanceToDuration(float distance, double expectedDuration, HitObject? referenceObject = null) - => AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceSnapProvider.DistanceToDuration(referenceObject ?? new HitObject(), distance), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON)); + => AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceSnapProvider.DistanceToDuration(distance, referenceObject?.StartTime ?? 0, referenceObject as IHasSliderVelocity), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON)); private void assertSnappedDuration(float distance, double expectedDuration, HitObject? referenceObject = null) - => AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDuration(referenceObject ?? new HitObject(), distance), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON)); + => AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDuration(distance, referenceObject?.StartTime ?? 0, referenceObject as IHasSliderVelocity), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON)); private void assertSnappedDistance(float distance, float expectedDistance, HitObject? referenceObject = null) - => AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDistance(referenceObject ?? new HitObject(), distance, DistanceSnapTarget.End), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON)); + => AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDistance(distance, referenceObject?.GetEndTime() ?? 0, referenceObject as IHasSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON)); private partial class TestHitObjectComposer : OsuHitObjectComposer { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index 818862d958..51e4f526a1 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Screens.Edit; @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Visual.Editing public new int MaxIntervals => base.MaxIntervals; public TestDistanceSnapGrid(double? endTime = null) - : base(new HitObject(), grid_position, 0, endTime) + : base(grid_position, 0, endTime) { } @@ -191,15 +191,15 @@ namespace osu.Game.Tests.Visual.Editing Bindable IDistanceSnapProvider.DistanceSpacingMultiplier => DistanceSpacingMultiplier; - public float GetBeatSnapDistanceAt(HitObject referenceObject, bool useReferenceSliderVelocity = true) => beat_snap_distance; + public float GetBeatSnapDistance(IHasSliderVelocity withVelocity = null) => beat_snap_distance; - public float DurationToDistance(HitObject referenceObject, double duration) => (float)duration; + public float DurationToDistance(double duration, double timingReference, IHasSliderVelocity withVelocity = null) => (float)duration; - public double DistanceToDuration(HitObject referenceObject, float distance) => distance; + public double DistanceToDuration(float distance, double timingReference, IHasSliderVelocity withVelocity = null) => distance; - public double FindSnappedDuration(HitObject referenceObject, float distance) => 0; + public double FindSnappedDuration(float distance, double snapReferenceTime, IHasSliderVelocity withVelocity = null) => 0; - public float FindSnappedDistance(HitObject referenceObject, float distance, DistanceSnapTarget target) => 0; + public float FindSnappedDistance(float distance, double snapReferenceTime, IHasSliderVelocity? withVelocity = null) => 0; } } } diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index 0ca01ccee6..997d1f927b 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -265,57 +265,41 @@ namespace osu.Game.Rulesets.Edit #region IDistanceSnapProvider - public virtual float GetBeatSnapDistanceAt(HitObject referenceObject, bool useReferenceSliderVelocity = true) + public virtual float GetBeatSnapDistance(IHasSliderVelocity? withVelocity = null) { - return (float)(100 * (useReferenceSliderVelocity && referenceObject is IHasSliderVelocity hasSliderVelocity ? hasSliderVelocity.SliderVelocityMultiplier : 1) * editorBeatmap.Difficulty.SliderMultiplier * 1 + return (float)(100 * (withVelocity?.SliderVelocityMultiplier ?? 1) * editorBeatmap.Difficulty.SliderMultiplier * 1 / beatSnapProvider.BeatDivisor); } - public virtual float DurationToDistance(HitObject referenceObject, double duration) + public virtual float DurationToDistance(double duration, double timingReference, IHasSliderVelocity? withVelocity = null) { - double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime); - return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceObject)); + double beatLength = beatSnapProvider.GetBeatLengthAtTime(timingReference); + return (float)(duration / beatLength * GetBeatSnapDistance(withVelocity)); } - public virtual double DistanceToDuration(HitObject referenceObject, float distance) + public virtual double DistanceToDuration(float distance, double timingReference, IHasSliderVelocity? withVelocity = null) { - double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceObject.StartTime); - return distance / GetBeatSnapDistanceAt(referenceObject) * beatLength; + double beatLength = beatSnapProvider.GetBeatLengthAtTime(timingReference); + return distance / GetBeatSnapDistance(withVelocity) * beatLength; } - public virtual double FindSnappedDuration(HitObject referenceObject, float distance) - => beatSnapProvider.SnapTime(referenceObject.StartTime + DistanceToDuration(referenceObject, distance), referenceObject.StartTime) - referenceObject.StartTime; + public virtual double FindSnappedDuration(float distance, double snapReferenceTime, IHasSliderVelocity? withVelocity = null) + => beatSnapProvider.SnapTime(snapReferenceTime + DistanceToDuration(distance, snapReferenceTime, withVelocity), snapReferenceTime) - snapReferenceTime; - public virtual float FindSnappedDistance(HitObject referenceObject, float distance, DistanceSnapTarget target) + public virtual float FindSnappedDistance(float distance, double snapReferenceTime, IHasSliderVelocity? withVelocity = null) { - double referenceTime; + double actualDuration = snapReferenceTime + DistanceToDuration(distance, snapReferenceTime, withVelocity); - switch (target) - { - case DistanceSnapTarget.Start: - referenceTime = referenceObject.StartTime; - break; + double snappedTime = beatSnapProvider.SnapTime(actualDuration, snapReferenceTime); - case DistanceSnapTarget.End: - referenceTime = referenceObject.GetEndTime(); - break; - - default: - throw new ArgumentOutOfRangeException(nameof(target), target, $"Unknown {nameof(DistanceSnapTarget)} value"); - } - - double actualDuration = referenceTime + DistanceToDuration(referenceObject, distance); - - double snappedTime = beatSnapProvider.SnapTime(actualDuration, referenceTime); - - double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceTime); + double beatLength = beatSnapProvider.GetBeatLengthAtTime(snapReferenceTime); // we don't want to exceed the actual duration and snap to a point in the future. // as we are snapping to beat length via SnapTime (which will round-to-nearest), check for snapping in the forward direction and reverse it. if (snappedTime > actualDuration + 1) snappedTime -= beatLength; - return DurationToDistance(referenceObject, snappedTime - referenceTime); + return DurationToDistance(snappedTime - snapReferenceTime, snapReferenceTime, withVelocity); } #endregion diff --git a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs index 612e09d3ea..99a9083273 100644 --- a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs @@ -4,7 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Edit { @@ -22,53 +22,74 @@ namespace osu.Game.Rulesets.Edit Bindable DistanceSpacingMultiplier { get; } /// - /// Retrieves the distance between two points within a timing point that are one beat length apart. + /// Returns the spatial distance between objects which are temporally one beat apart. + /// Depends on: + /// + /// the slider velocity taken from , + /// the beatmap's ,, + /// the current beat divisor. + /// + /// Note that the returned value does NOT depend on ; + /// consumers are expected to include that multiplier as they see fit. /// - /// 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, bool useReferenceSliderVelocity = true); + float GetBeatSnapDistance(IHasSliderVelocity? withVelocity = null); /// - /// Converts a duration to a distance without applying any snapping. + /// Converts a temporal duration into a spatial distance. + /// Does not perform any snapping. + /// Depends on: + /// + /// the provided, + /// a used to retrieve the beat length of the beatmap at that time, + /// the slider velocity taken from , + /// the beatmap's ,, + /// the current beat divisor. + /// + /// Note that the returned value does NOT depend on ; + /// consumers are expected to include that multiplier as they see fit. /// - /// An object to be used as a reference point for this operation. - /// The duration to convert. - /// A value that represents as a distance in the timing point. - float DurationToDistance(HitObject referenceObject, double duration); + float DurationToDistance(double duration, double timingReference, IHasSliderVelocity? withVelocity = null); /// - /// Converts a distance to a duration without applying any snapping. + /// Converts a spatial distance into a temporal duration. + /// Does not perform any snapping. + /// Depends on: + /// + /// the provided, + /// a used to retrieve the beat length of the beatmap at that time, + /// the slider velocity taken from , + /// the beatmap's ,, + /// the current beat divisor. + /// + /// Note that the returned value does NOT depend on ; + /// consumers are expected to include that multiplier as they see fit. /// - /// An object to be used as a reference point for this operation. - /// The distance to convert. - /// A value that represents as a duration in the timing point. - double DistanceToDuration(HitObject referenceObject, float distance); + double DistanceToDuration(float distance, double timingReference, IHasSliderVelocity? withVelocity = null); /// - /// Given a distance from the provided hit object, find the valid snapped duration. + /// Converts a spatial distance into a temporal duration and then snaps said duration to the beat, relative to . + /// Depends on: + /// + /// the provided, + /// a used to retrieve the beat length of the beatmap at that time, + /// the slider velocity taken from , + /// the beatmap's ,, + /// the current beat divisor. + /// /// - /// An object to be used as a reference point for this operation. - /// The distance to convert. - /// A value that represents as a duration snapped to the closest beat of the timing point. - double FindSnappedDuration(HitObject referenceObject, float distance); + double FindSnappedDuration(float distance, double snapReferenceTime, IHasSliderVelocity? withVelocity = null); /// - /// Given a distance from the provided hit object, find the valid snapped distance. + /// Snaps a spatial distance to the beat, relative to . + /// Depends on: + /// + /// the provided, + /// a used to retrieve the beat length of the beatmap at that time, + /// the slider velocity taken from , + /// the beatmap's ,, + /// the current beat divisor. + /// /// - /// An object to be used as a reference point for this operation. - /// The distance to convert. - /// Whether the distance measured should be from the start or the end of . - /// - /// A value that represents snapped to the closest beat of the timing point. - /// The distance will always be less than or equal to the provided . - /// - float FindSnappedDistance(HitObject referenceObject, float distance, DistanceSnapTarget target); - } - - public enum DistanceSnapTarget - { - Start, - End, + float FindSnappedDistance(float distance, double snapReferenceTime, IHasSliderVelocity? withVelocity = null); } } diff --git a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs index a631274f74..4ce8166421 100644 --- a/osu.Game/Rulesets/Objects/SliderPathExtensions.cs +++ b/osu.Game/Rulesets/Objects/SliderPathExtensions.cs @@ -15,9 +15,9 @@ namespace osu.Game.Rulesets.Objects /// Snaps the provided 's duration using the . /// public static void SnapTo(this THitObject hitObject, IDistanceSnapProvider? snapProvider) - where THitObject : HitObject, IHasPath + where THitObject : HitObject, IHasPath, IHasSliderVelocity { - hitObject.Path.ExpectedDistance.Value = snapProvider?.FindSnappedDistance(hitObject, (float)hitObject.Path.CalculatedDistance, DistanceSnapTarget.Start) ?? hitObject.Path.CalculatedDistance; + hitObject.Path.ExpectedDistance.Value = snapProvider?.FindSnappedDistance((float)hitObject.Path.CalculatedDistance, hitObject.StartTime, hitObject) ?? hitObject.Path.CalculatedDistance; } /// diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index e84c2ebc35..9ddf54b779 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -8,7 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osuTK; using osuTK.Graphics; @@ -16,8 +16,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { public abstract partial class CircularDistanceSnapGrid : DistanceSnapGrid { - protected CircularDistanceSnapGrid(HitObject referenceObject, Vector2 startPosition, double startTime, double? endTime = null) - : base(referenceObject, startPosition, startTime, endTime) + protected CircularDistanceSnapGrid(Vector2 startPosition, double startTime, double? endTime = null, IHasSliderVelocity? sliderVelocitySource = null) + : base(startPosition, startTime, endTime, sliderVelocitySource) { } @@ -56,14 +56,14 @@ namespace osu.Game.Screens.Edit.Compose.Components // Picture the scenario where the user has just placed an object on a 1/2 snap, then changes to // 1/3 snap and expects to be able to place the next object on a valid 1/3 snap, regardless of the // fact that the 1/2 snap reference object is not valid for 1/3 snapping. - float offset = SnapProvider.FindSnappedDistance(ReferenceObject, 0, DistanceSnapTarget.End); + float offset = SnapProvider.FindSnappedDistance(0, StartTime, SliderVelocitySource); for (int i = 0; i < requiredCircles; i++) { const float thickness = 4; float diameter = (offset + (i + 1) * DistanceBetweenTicks + thickness / 2) * 2; - AddInternal(new Ring(ReferenceObject, GetColourForIndexFromPlacement(i)) + AddInternal(new Ring(StartTime, GetColourForIndexFromPlacement(i)) { Position = StartPosition, Origin = Anchor.Centre, @@ -98,19 +98,19 @@ namespace osu.Game.Screens.Edit.Compose.Components travelLength = DistanceBetweenTicks; float snappedDistance = fixedTime != null - ? SnapProvider.DurationToDistance(ReferenceObject, fixedTime.Value - ReferenceObject.GetEndTime()) + ? SnapProvider.DurationToDistance(fixedTime.Value - StartTime, StartTime, SliderVelocitySource) // When interacting with the resolved snap provider, the distance spacing multiplier should first be removed // to allow for snapping at a non-multiplied ratio. - : SnapProvider.FindSnappedDistance(ReferenceObject, travelLength / distanceSpacingMultiplier, DistanceSnapTarget.End); + : SnapProvider.FindSnappedDistance(travelLength / distanceSpacingMultiplier, StartTime, SliderVelocitySource); - double snappedTime = StartTime + SnapProvider.DistanceToDuration(ReferenceObject, snappedDistance); + double snappedTime = StartTime + SnapProvider.DistanceToDuration(snappedDistance, StartTime, SliderVelocitySource); if (snappedTime > LatestEndTime) { double tickLength = Beatmap.GetBeatLengthAtTime(StartTime); - snappedDistance = SnapProvider.DurationToDistance(ReferenceObject, MaxIntervals * tickLength); - snappedTime = StartTime + SnapProvider.DistanceToDuration(ReferenceObject, snappedDistance); + snappedDistance = SnapProvider.DurationToDistance(MaxIntervals * tickLength, StartTime, SliderVelocitySource); + snappedTime = StartTime + SnapProvider.DistanceToDuration(snappedDistance, StartTime, SliderVelocitySource); } // The multiplier can then be reapplied to the final position. @@ -127,13 +127,13 @@ namespace osu.Game.Screens.Edit.Compose.Components [Resolved] private EditorClock? editorClock { get; set; } - private readonly HitObject referenceObject; + private readonly double startTime; private readonly Color4 baseColour; - public Ring(HitObject referenceObject, Color4 baseColour) + public Ring(double startTime, Color4 baseColour) { - this.referenceObject = referenceObject; + this.startTime = startTime; Colour = this.baseColour = baseColour; @@ -148,9 +148,9 @@ namespace osu.Game.Screens.Edit.Compose.Components return; float distanceSpacingMultiplier = (float)snapProvider.DistanceSpacingMultiplier.Value; - double timeFromReferencePoint = editorClock.CurrentTime - referenceObject.GetEndTime(); + double timeFromReferencePoint = editorClock.CurrentTime - startTime; - float distanceForCurrentTime = snapProvider.DurationToDistance(referenceObject, timeFromReferencePoint) + float distanceForCurrentTime = snapProvider.DurationToDistance(timeFromReferencePoint, startTime) * distanceSpacingMultiplier; float timeBasedAlpha = 1 - Math.Clamp(Math.Abs(distanceForCurrentTime - Size.X / 2) / 30, 0, 1); diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index aaf58e0f7a..dd1671cfdd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -12,7 +13,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Layout; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osuTK; using osuTK.Graphics; @@ -48,6 +49,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected readonly double? LatestEndTime; + [CanBeNull] + protected readonly IHasSliderVelocity SliderVelocitySource; + [Resolved] protected OsuColour Colours { get; private set; } @@ -62,19 +66,17 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly LayoutValue gridCache = new LayoutValue(Invalidation.RequiredParentSizeToFit); - protected readonly HitObject ReferenceObject; - /// /// Creates a new . /// - /// A reference object to gather relevant difficulty values from. /// The position at which the grid should start. The first tick is located one distance spacing length away from this point. /// The snapping time at . /// The time at which the snapping grid should end. If null, the grid will continue until the bounds of the screen are exceeded. - protected DistanceSnapGrid(HitObject referenceObject, Vector2 startPosition, double startTime, double? endTime = null) + /// The reference object with slider velocity to include in the calculations for distance snapping. + protected DistanceSnapGrid(Vector2 startPosition, double startTime, double? endTime = null, [CanBeNull] IHasSliderVelocity sliderVelocitySource = null) { - ReferenceObject = referenceObject; LatestEndTime = endTime; + SliderVelocitySource = sliderVelocitySource; StartPosition = startPosition; StartTime = startTime; @@ -97,14 +99,14 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updateSpacing() { float distanceSpacingMultiplier = (float)DistanceSpacingMultiplier.Value; - float beatSnapDistance = SnapProvider.GetBeatSnapDistanceAt(ReferenceObject, false); + float beatSnapDistance = SnapProvider.GetBeatSnapDistance(SliderVelocitySource); DistanceBetweenTicks = beatSnapDistance * distanceSpacingMultiplier; if (LatestEndTime == null) MaxIntervals = int.MaxValue; else - MaxIntervals = (int)((LatestEndTime.Value - StartTime) / SnapProvider.DistanceToDuration(ReferenceObject, beatSnapDistance)); + MaxIntervals = (int)((LatestEndTime.Value - StartTime) / SnapProvider.DistanceToDuration(beatSnapDistance, StartTime, SliderVelocitySource)); gridCache.Invalidate(); } @@ -132,7 +134,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// The original position in coordinate space local to this . /// /// Whether the snap operation should be temporally constrained to a particular time instant, - /// thus fixing the possible positions to a set distance from the . + /// thus fixing the possible positions to a set distance relative from the . /// /// A tuple containing the snapped position in coordinate space local to this and the respective time value. public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position, double? fixedTime = null); From df37768ff4075ca4de2c4afee377967d745d5e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Feb 2025 13:55:04 +0100 Subject: [PATCH 2/4] Remove unused method Only used in test code. --- ...tSceneHitObjectComposerDistanceSnapping.cs | 37 ------------------- .../Editing/TestSceneDistanceSnapGrid.cs | 2 - .../Edit/ComposerDistanceSnapProvider.cs | 3 -- .../Rulesets/Edit/IDistanceSnapProvider.cs | 13 ------- 4 files changed, 55 deletions(-) diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index af116ad334..408db39d54 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -107,7 +107,6 @@ namespace osu.Game.Tests.Editing assertSnapDistance(base_distance * slider_velocity, referenceObject); 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); @@ -155,39 +154,6 @@ namespace osu.Game.Tests.Editing assertDistanceToDuration(400, 1000); } - [Test] - public void TestGetSnappedDurationFromDistance() - { - assertSnappedDuration(0, 0); - assertSnappedDuration(50, 1000); - assertSnappedDuration(100, 1000); - assertSnappedDuration(150, 2000); - assertSnappedDuration(200, 2000); - assertSnappedDuration(250, 3000); - - AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = 2); - - assertSnappedDuration(0, 0); - assertSnappedDuration(50, 0); - assertSnappedDuration(100, 1000); - assertSnappedDuration(150, 1000); - assertSnappedDuration(200, 1000); - assertSnappedDuration(250, 1000); - - AddStep("set beat length = 500", () => - { - composer.EditorBeatmap.ControlPointInfo.Clear(); - composer.EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 }); - }); - - assertSnappedDuration(50, 0); - assertSnappedDuration(100, 500); - assertSnappedDuration(150, 500); - assertSnappedDuration(200, 500); - assertSnappedDuration(250, 500); - assertSnappedDuration(400, 1000); - } - [Test] public void GetSnappedDistanceFromDistance() { @@ -289,9 +255,6 @@ namespace osu.Game.Tests.Editing private void assertDistanceToDuration(float distance, double expectedDuration, HitObject? referenceObject = null) => AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceSnapProvider.DistanceToDuration(distance, referenceObject?.StartTime ?? 0, referenceObject as IHasSliderVelocity), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON)); - private void assertSnappedDuration(float distance, double expectedDuration, HitObject? referenceObject = null) - => AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDuration(distance, referenceObject?.StartTime ?? 0, referenceObject as IHasSliderVelocity), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON)); - private void assertSnappedDistance(float distance, float expectedDistance, HitObject? referenceObject = null) => AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDistance(distance, referenceObject?.GetEndTime() ?? 0, referenceObject as IHasSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON)); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index 51e4f526a1..af02333468 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -197,8 +197,6 @@ namespace osu.Game.Tests.Visual.Editing public double DistanceToDuration(float distance, double timingReference, IHasSliderVelocity withVelocity = null) => distance; - public double FindSnappedDuration(float distance, double snapReferenceTime, IHasSliderVelocity withVelocity = null) => 0; - public float FindSnappedDistance(float distance, double snapReferenceTime, IHasSliderVelocity? withVelocity = null) => 0; } } diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index 997d1f927b..d0b279f201 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -283,9 +283,6 @@ namespace osu.Game.Rulesets.Edit return distance / GetBeatSnapDistance(withVelocity) * beatLength; } - public virtual double FindSnappedDuration(float distance, double snapReferenceTime, IHasSliderVelocity? withVelocity = null) - => beatSnapProvider.SnapTime(snapReferenceTime + DistanceToDuration(distance, snapReferenceTime, withVelocity), snapReferenceTime) - snapReferenceTime; - public virtual float FindSnappedDistance(float distance, double snapReferenceTime, IHasSliderVelocity? withVelocity = null) { double actualDuration = snapReferenceTime + DistanceToDuration(distance, snapReferenceTime, withVelocity); diff --git a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs index 99a9083273..8006db14a3 100644 --- a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs @@ -66,19 +66,6 @@ namespace osu.Game.Rulesets.Edit /// double DistanceToDuration(float distance, double timingReference, IHasSliderVelocity? withVelocity = null); - /// - /// Converts a spatial distance into a temporal duration and then snaps said duration to the beat, relative to . - /// Depends on: - /// - /// the provided, - /// a used to retrieve the beat length of the beatmap at that time, - /// the slider velocity taken from , - /// the beatmap's ,, - /// the current beat divisor. - /// - /// - double FindSnappedDuration(float distance, double snapReferenceTime, IHasSliderVelocity? withVelocity = null); - /// /// Snaps a spatial distance to the beat, relative to . /// Depends on: From 2d6f64e89185e71d755201c52c951236116a0fe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 3 Feb 2025 15:17:32 +0100 Subject: [PATCH 3/4] Fix code quality --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs | 2 +- osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 60c37cd4a4..b3e23daa99 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -408,7 +408,7 @@ namespace osu.Game.Rulesets.Osu.Edit ArgumentOutOfRangeException.ThrowIfNegativeOrZero(targetOffset); int positionSourceObjectIndex = -1; - IHasSliderVelocity? sliderVelocitySource = null; + IHasSliderVelocity sliderVelocitySource = null; for (int i = 0; i < EditorBeatmap.HitObjects.Count; i++) { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index af02333468..fb57422e66 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -197,7 +197,7 @@ namespace osu.Game.Tests.Visual.Editing public double DistanceToDuration(float distance, double timingReference, IHasSliderVelocity withVelocity = null) => distance; - public float FindSnappedDistance(float distance, double snapReferenceTime, IHasSliderVelocity? withVelocity = null) => 0; + public float FindSnappedDistance(float distance, double snapReferenceTime, IHasSliderVelocity withVelocity = null) => 0; } } } diff --git a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs index 8006db14a3..195dbf0d46 100644 --- a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Edit double DistanceToDuration(float distance, double timingReference, IHasSliderVelocity? withVelocity = null); /// - /// Snaps a spatial distance to the beat, relative to . + /// Snaps a spatial distance to the beat, relative to . /// Depends on: /// /// the provided, From 731f100aaf656ae8273412dc8bdb3134415d4889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 4 Feb 2025 11:45:15 +0100 Subject: [PATCH 4/4] Fix incorrect snapping behaviour when previous object is not snapped to beat --- osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs | 2 ++ .../Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs index 195dbf0d46..bb0a0dbd7f 100644 --- a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs @@ -76,6 +76,8 @@ namespace osu.Game.Rulesets.Edit /// the beatmap's ,, /// the current beat divisor. /// + /// Note that the returned value does NOT depend on ; + /// consumers are expected to include that multiplier as they see fit. /// float FindSnappedDistance(float distance, double snapReferenceTime, IHasSliderVelocity? withVelocity = null); } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 9ddf54b779..164a209958 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // Picture the scenario where the user has just placed an object on a 1/2 snap, then changes to // 1/3 snap and expects to be able to place the next object on a valid 1/3 snap, regardless of the // fact that the 1/2 snap reference object is not valid for 1/3 snapping. - float offset = SnapProvider.FindSnappedDistance(0, StartTime, SliderVelocitySource); + float offset = (float)(SnapProvider.FindSnappedDistance(0, StartTime, SliderVelocitySource) * DistanceSpacingMultiplier.Value); for (int i = 0; i < requiredCircles; i++) {