mirror of
https://github.com/ppy/osu.git
synced 2025-02-08 01:13:21 +08:00
Merge pull request #31789 from bdach/distance-snap-fix
Fix distance snap grid not properly working due to multiple issues
This commit is contained in:
commit
c37fa261c3
@ -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);
|
||||
|
@ -34,7 +34,7 @@ using osuTK.Input;
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
public partial class PathControlPointVisualiser<T> : CompositeDrawable, IKeyBindingHandler<PlatformAction>, 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.
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -114,9 +105,8 @@ 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);
|
||||
|
||||
assertDistanceToDuration(base_distance * slider_velocity, 1000, referenceObject);
|
||||
assertDurationToDistance(1000, base_distance * slider_velocity, referenceObject);
|
||||
@ -164,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,20 +246,17 @@ 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));
|
||||
|
||||
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}", () => composer.DistanceSnapProvider.DistanceToDuration(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
|
||||
{
|
||||
|
@ -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,13 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
Bindable<double> 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 float FindSnappedDistance(HitObject referenceObject, float distance, DistanceSnapTarget target) => 0;
|
||||
public float FindSnappedDistance(float distance, double snapReferenceTime, IHasSliderVelocity withVelocity = null) => 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -265,57 +265,38 @@ 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 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
|
||||
|
@ -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,63 @@ namespace osu.Game.Rulesets.Edit
|
||||
Bindable<double> DistanceSpacingMultiplier { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 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:
|
||||
/// <list type="bullet">
|
||||
/// <item>the slider velocity taken from <paramref name="withVelocity"/>,</item>
|
||||
/// <item>the beatmap's <see cref="IBeatmapDifficultyInfo.SliderMultiplier"/>,</item>,
|
||||
/// <item>the current beat divisor.</item>
|
||||
/// </list>
|
||||
/// Note that the returned value does <b>NOT</b> depend on <see cref="DistanceSpacingMultiplier"/>;
|
||||
/// consumers are expected to include that multiplier as they see fit.
|
||||
/// </summary>
|
||||
/// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
|
||||
/// <param name="useReferenceSliderVelocity">Whether the <paramref name="referenceObject"/>'s slider velocity should be factored into the returned distance.</param>
|
||||
/// <returns>The distance between two points residing in the timing point that are one beat length apart.</returns>
|
||||
float GetBeatSnapDistanceAt(HitObject referenceObject, bool useReferenceSliderVelocity = true);
|
||||
float GetBeatSnapDistance(IHasSliderVelocity? withVelocity = null);
|
||||
|
||||
/// <summary>
|
||||
/// 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:
|
||||
/// <list type="bullet">
|
||||
/// <item>the <paramref name="duration"/> provided,</item>
|
||||
/// <item>a <paramref name="timingReference"/> used to retrieve the beat length of the beatmap at that time,</item>
|
||||
/// <item>the slider velocity taken from <paramref name="withVelocity"/>,</item>
|
||||
/// <item>the beatmap's <see cref="IBeatmapDifficultyInfo.SliderMultiplier"/>,</item>,
|
||||
/// <item>the current beat divisor.</item>
|
||||
/// </list>
|
||||
/// Note that the returned value does <b>NOT</b> depend on <see cref="DistanceSpacingMultiplier"/>;
|
||||
/// consumers are expected to include that multiplier as they see fit.
|
||||
/// </summary>
|
||||
/// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
|
||||
/// <param name="duration">The duration to convert.</param>
|
||||
/// <returns>A value that represents <paramref name="duration"/> as a distance in the timing point.</returns>
|
||||
float DurationToDistance(HitObject referenceObject, double duration);
|
||||
float DurationToDistance(double duration, double timingReference, IHasSliderVelocity? withVelocity = null);
|
||||
|
||||
/// <summary>
|
||||
/// 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:
|
||||
/// <list type="bullet">
|
||||
/// <item>the <paramref name="distance"/> provided,</item>
|
||||
/// <item>a <paramref name="timingReference"/> used to retrieve the beat length of the beatmap at that time,</item>
|
||||
/// <item>the slider velocity taken from <paramref name="withVelocity"/>,</item>
|
||||
/// <item>the beatmap's <see cref="IBeatmapDifficultyInfo.SliderMultiplier"/>,</item>,
|
||||
/// <item>the current beat divisor.</item>
|
||||
/// </list>
|
||||
/// Note that the returned value does <b>NOT</b> depend on <see cref="DistanceSpacingMultiplier"/>;
|
||||
/// consumers are expected to include that multiplier as they see fit.
|
||||
/// </summary>
|
||||
/// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
|
||||
/// <param name="distance">The distance to convert.</param>
|
||||
/// <returns>A value that represents <paramref name="distance"/> as a duration in the timing point.</returns>
|
||||
double DistanceToDuration(HitObject referenceObject, float distance);
|
||||
double DistanceToDuration(float distance, double timingReference, IHasSliderVelocity? withVelocity = null);
|
||||
|
||||
/// <summary>
|
||||
/// Given a distance from the provided hit object, find the valid snapped duration.
|
||||
/// Snaps a spatial distance to the beat, relative to <paramref name="snapReferenceTime"/>.
|
||||
/// Depends on:
|
||||
/// <list type="bullet">
|
||||
/// <item>the <paramref name="distance"/> provided,</item>
|
||||
/// <item>a <paramref name="snapReferenceTime"/> used to retrieve the beat length of the beatmap at that time,</item>
|
||||
/// <item>the slider velocity taken from <paramref name="withVelocity"/>,</item>
|
||||
/// <item>the beatmap's <see cref="IBeatmapDifficultyInfo.SliderMultiplier"/>,</item>,
|
||||
/// <item>the current beat divisor.</item>
|
||||
/// </list>
|
||||
/// Note that the returned value does <b>NOT</b> depend on <see cref="DistanceSpacingMultiplier"/>;
|
||||
/// consumers are expected to include that multiplier as they see fit.
|
||||
/// </summary>
|
||||
/// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
|
||||
/// <param name="distance">The distance to convert.</param>
|
||||
/// <returns>A value that represents <paramref name="distance"/> as a duration snapped to the closest beat of the timing point.</returns>
|
||||
double FindSnappedDuration(HitObject referenceObject, float distance);
|
||||
|
||||
/// <summary>
|
||||
/// Given a distance from the provided hit object, find the valid snapped distance.
|
||||
/// </summary>
|
||||
/// <param name="referenceObject">An object to be used as a reference point for this operation.</param>
|
||||
/// <param name="distance">The distance to convert.</param>
|
||||
/// <param name="target">Whether the distance measured should be from the start or the end of <paramref name="referenceObject"/>.</param>
|
||||
/// <returns>
|
||||
/// A value that represents <paramref name="distance"/> snapped to the closest beat of the timing point.
|
||||
/// The distance will always be less than or equal to the provided <paramref name="distance"/>.
|
||||
/// </returns>
|
||||
float FindSnappedDistance(HitObject referenceObject, float distance, DistanceSnapTarget target);
|
||||
}
|
||||
|
||||
public enum DistanceSnapTarget
|
||||
{
|
||||
Start,
|
||||
End,
|
||||
float FindSnappedDistance(float distance, double snapReferenceTime, IHasSliderVelocity? withVelocity = null);
|
||||
}
|
||||
}
|
||||
|
@ -15,9 +15,9 @@ namespace osu.Game.Rulesets.Objects
|
||||
/// Snaps the provided <paramref name="hitObject"/>'s duration using the <paramref name="snapProvider"/>.
|
||||
/// </summary>
|
||||
public static void SnapTo<THitObject>(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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -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 = (float)(SnapProvider.FindSnappedDistance(0, StartTime, SliderVelocitySource) * DistanceSpacingMultiplier.Value);
|
||||
|
||||
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);
|
||||
|
@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="DistanceSnapGrid"/>.
|
||||
/// </summary>
|
||||
/// <param name="referenceObject">A reference object to gather relevant difficulty values from.</param>
|
||||
/// <param name="startPosition">The position at which the grid should start. The first tick is located one distance spacing length away from this point.</param>
|
||||
/// <param name="startTime">The snapping time at <see cref="StartPosition"/>.</param>
|
||||
/// <param name="endTime">The time at which the snapping grid should end. If null, the grid will continue until the bounds of the screen are exceeded.</param>
|
||||
protected DistanceSnapGrid(HitObject referenceObject, Vector2 startPosition, double startTime, double? endTime = null)
|
||||
/// <param name="sliderVelocitySource">The reference object with slider velocity to include in the calculations for distance snapping.</param>
|
||||
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
|
||||
/// <param name="position">The original position in coordinate space local to this <see cref="DistanceSnapGrid"/>.</param>
|
||||
/// <param name="fixedTime">
|
||||
/// Whether the snap operation should be temporally constrained to a particular time instant,
|
||||
/// thus fixing the possible positions to a set distance from the <see cref="ReferenceObject"/>.
|
||||
/// thus fixing the possible positions to a set distance relative from the <see cref="StartTime"/>.
|
||||
/// </param>
|
||||
/// <returns>A tuple containing the snapped position in coordinate space local to this <see cref="DistanceSnapGrid"/> and the respective time value.</returns>
|
||||
public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position, double? fixedTime = null);
|
||||
|
Loading…
Reference in New Issue
Block a user