1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-15 16:27:43 +08:00

Fix distance spacing grid displaying incorrectly for unsnapped objects with duration

This commit is contained in:
Bartłomiej Dach 2024-09-30 12:26:08 +02:00
parent 4723efaf41
commit 75fc57c34b
No known key found for this signature in database
8 changed files with 37 additions and 16 deletions

View File

@ -401,7 +401,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) ?? (float)HitObject.Path.CalculatedDistance;
HitObject.Path.ExpectedDistance.Value = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)HitObject.Path.CalculatedDistance, DistanceSnapTarget.Start) ?? (float)HitObject.Path.CalculatedDistance;
bodyPiece.UpdateFrom(HitObject);
headCirclePiece.UpdateFrom(HitObject.HeadCircle);

View File

@ -269,7 +269,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
double minDistance = distanceSnapProvider?.GetBeatSnapDistanceAt(HitObject, false) * 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) ?? proposedDistance;
proposedDistance = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)proposedDistance + 1, DistanceSnapTarget.Start) ?? proposedDistance;
proposedDistance = MathHelper.Clamp(proposedDistance, minDistance, HitObject.Path.CalculatedDistance);
}

View File

@ -298,7 +298,7 @@ namespace osu.Game.Tests.Editing
=> AddAssert($"distance = {distance} -> duration = {expectedDuration} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDuration(referenceObject ?? new HitObject(), distance), () => 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), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
=> AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDistance(referenceObject ?? new HitObject(), distance, DistanceSnapTarget.End), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
private partial class TestHitObjectComposer : OsuHitObjectComposer
{

View File

@ -199,7 +199,7 @@ namespace osu.Game.Tests.Visual.Editing
public double FindSnappedDuration(HitObject referenceObject, float distance) => 0;
public float FindSnappedDistance(HitObject referenceObject, float distance) => 0;
public float FindSnappedDistance(HitObject referenceObject, float distance, DistanceSnapTarget target) => 0;
}
}
}

View File

@ -280,22 +280,36 @@ namespace osu.Game.Rulesets.Edit
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)
public virtual float FindSnappedDistance(HitObject referenceObject, float distance, DistanceSnapTarget target)
{
double startTime = referenceObject.StartTime;
double referenceTime;
double actualDuration = startTime + DistanceToDuration(referenceObject, distance);
switch (target)
{
case DistanceSnapTarget.Start:
referenceTime = referenceObject.StartTime;
break;
double snappedEndTime = beatSnapProvider.SnapTime(actualDuration, startTime);
case DistanceSnapTarget.End:
referenceTime = referenceObject.GetEndTime();
break;
double beatLength = beatSnapProvider.GetBeatLengthAtTime(startTime);
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);
// 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 (snappedEndTime > actualDuration + 1)
snappedEndTime -= beatLength;
if (snappedTime > actualDuration + 1)
snappedTime -= beatLength;
return DurationToDistance(referenceObject, snappedEndTime - startTime);
return DurationToDistance(referenceObject, snappedTime - referenceTime);
}
#endregion

View File

@ -58,10 +58,17 @@ namespace osu.Game.Rulesets.Edit
/// </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);
float FindSnappedDistance(HitObject referenceObject, float distance, DistanceSnapTarget target);
}
public enum DistanceSnapTarget
{
Start,
End,
}
}

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Objects
public static void SnapTo<THitObject>(this THitObject hitObject, IDistanceSnapProvider? snapProvider)
where THitObject : HitObject, IHasPath
{
hitObject.Path.ExpectedDistance.Value = snapProvider?.FindSnappedDistance(hitObject, (float)hitObject.Path.CalculatedDistance) ?? hitObject.Path.CalculatedDistance;
hitObject.Path.ExpectedDistance.Value = snapProvider?.FindSnappedDistance(hitObject, (float)hitObject.Path.CalculatedDistance, DistanceSnapTarget.Start) ?? hitObject.Path.CalculatedDistance;
}
/// <summary>

View File

@ -59,7 +59,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(ReferenceObject, 0);
float offset = SnapProvider.FindSnappedDistance(ReferenceObject, 0, DistanceSnapTarget.End);
for (int i = 0; i < requiredCircles; i++)
{
@ -104,7 +104,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
? SnapProvider.DurationToDistance(ReferenceObject, editorClock.CurrentTime - ReferenceObject.GetEndTime())
// 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);
: SnapProvider.FindSnappedDistance(ReferenceObject, travelLength / distanceSpacingMultiplier, DistanceSnapTarget.End);
double snappedTime = StartTime + SnapProvider.DistanceToDuration(ReferenceObject, snappedDistance);