mirror of https://github.com/ppy/osu
Merge pull request #30062 from bdach/distance-snap-weirdness
Fix various distance snap grid weirdness around unsnapped objects
This commit is contained in:
commit
87ab953935
|
@ -401,7 +401,7 @@ private void updateSlider()
|
|||
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);
|
||||
|
|
|
@ -269,7 +269,7 @@ private void adjustLength(double proposedDistance, bool adjustVelocity)
|
|||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -228,6 +228,42 @@ public void GetSnappedDistanceFromDistance()
|
|||
assertSnappedDistance(400, 400);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUnsnappedObject()
|
||||
{
|
||||
var slider = new Slider
|
||||
{
|
||||
StartTime = 0,
|
||||
Path = new SliderPath
|
||||
{
|
||||
ControlPoints =
|
||||
{
|
||||
new PathControlPoint(),
|
||||
// simulate object snapped to 1/3rds
|
||||
// this object's end time will be 2000 / 3 = 666.66... ms
|
||||
new PathControlPoint(new Vector2(200 / 3f, 0)),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AddStep("add slider", () => composer.EditorBeatmap.Add(slider));
|
||||
AddStep("set snap to 1/4", () => BeatDivisor.Value = 4);
|
||||
|
||||
// with default beat length of 1000ms and snap at 1/4, the valid snap times are 500ms, 750ms, and 1000ms
|
||||
// with default settings, the snapped distance will be a tenth of the difference of the time delta
|
||||
|
||||
// (500 - 666.66...) / 10 = -16.66... = -100 / 6
|
||||
assertSnappedDistance(0, -100 / 6f, slider);
|
||||
assertSnappedDistance(7, -100 / 6f, slider);
|
||||
|
||||
// (750 - 666.66...) / 10 = 8.33... = 100 / 12
|
||||
assertSnappedDistance(9, 100 / 12f, slider);
|
||||
assertSnappedDistance(33, 100 / 12f, slider);
|
||||
|
||||
// (1000 - 666.66...) / 10 = 33.33... = 100 / 3
|
||||
assertSnappedDistance(34, 100 / 3f, slider);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUseCurrentSnap()
|
||||
{
|
||||
|
@ -263,7 +299,7 @@ private void assertSnappedDuration(float distance, double expectedDuration, HitO
|
|||
=> 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
|
||||
{
|
||||
|
|
|
@ -199,7 +199,7 @@ private class SnapProvider : IDistanceSnapProvider
|
|||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -280,22 +280,36 @@ public virtual double DistanceToDuration(HitObject referenceObject, float distan
|
|||
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
|
||||
|
|
|
@ -58,10 +58,17 @@ public interface IDistanceSnapProvider
|
|||
/// </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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ public static class SliderPathExtensions
|
|||
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>
|
||||
|
|
|
@ -59,7 +59,7 @@ protected override void CreateContent()
|
|||
// 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 @@ public override (Vector2 position, double time) GetSnappedPosition(Vector2 posit
|
|||
? 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);
|
||||
|
||||
|
|
|
@ -155,7 +155,7 @@ protected Color4 GetColourForIndexFromPlacement(int placementIndex)
|
|||
{
|
||||
var timingPoint = Beatmap.ControlPointInfo.TimingPointAt(StartTime);
|
||||
double beatLength = timingPoint.BeatLength / beatDivisor.Value;
|
||||
int beatIndex = (int)Math.Round((StartTime - timingPoint.Time) / beatLength);
|
||||
int beatIndex = (int)Math.Floor((StartTime - timingPoint.Time) / beatLength);
|
||||
|
||||
var colour = BindableBeatDivisor.GetColourFor(BindableBeatDivisor.GetDivisorForBeatIndex(beatIndex + placementIndex + 1, beatDivisor.Value), Colours);
|
||||
|
||||
|
|
Loading…
Reference in New Issue