1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 10:07:52 +08:00

Fix ticks in repeat spans being returned in reverse order (#5559)

Fix ticks in repeat spans being returned in reverse order
This commit is contained in:
Dean Herbert 2019-08-02 06:31:11 +02:00 committed by GitHub
commit 770b228fdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 770 additions and 16 deletions

View File

@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.Tests
[TestCase("basic")]
[TestCase("colinear-perfect-curve")]
[TestCase("slider-ticks")]
[TestCase("repeat-slider")]
[TestCase("uneven-repeat-slider")]
public new void Test(string name)
{
base.Test(name);

View File

@ -0,0 +1,222 @@
{
"Mappings": [{
"StartTime": 369,
"Objects": [{
"StartTime": 369,
"EndTime": 369,
"X": 177,
"Y": 191
},
{
"StartTime": 450,
"EndTime": 450,
"X": 216.539276,
"Y": 191.192871
},
{
"StartTime": 532,
"EndTime": 532,
"X": 256.5667,
"Y": 191.388138
},
{
"StartTime": 614,
"EndTime": 614,
"X": 296.594116,
"Y": 191.583389
},
{
"StartTime": 696,
"EndTime": 696,
"X": 336.621521,
"Y": 191.778641
},
{
"StartTime": 778,
"EndTime": 778,
"X": 376.648926,
"Y": 191.9739
},
{
"StartTime": 860,
"EndTime": 860,
"X": 337.318878,
"Y": 191.782043
},
{
"StartTime": 942,
"EndTime": 942,
"X": 297.291443,
"Y": 191.586792
},
{
"StartTime": 1024,
"EndTime": 1024,
"X": 257.264038,
"Y": 191.391541
},
{
"StartTime": 1106,
"EndTime": 1106,
"X": 217.2366,
"Y": 191.196274
},
{
"StartTime": 1188,
"EndTime": 1188,
"X": 177.209213,
"Y": 191.001022
},
{
"StartTime": 1270,
"EndTime": 1270,
"X": 216.818192,
"Y": 191.194229
},
{
"StartTime": 1352,
"EndTime": 1352,
"X": 256.8456,
"Y": 191.3895
},
{
"StartTime": 1434,
"EndTime": 1434,
"X": 296.873047,
"Y": 191.584747
},
{
"StartTime": 1516,
"EndTime": 1516,
"X": 336.900452,
"Y": 191.78
},
{
"StartTime": 1598,
"EndTime": 1598,
"X": 376.927917,
"Y": 191.975266
},
{
"StartTime": 1680,
"EndTime": 1680,
"X": 337.039948,
"Y": 191.780685
},
{
"StartTime": 1762,
"EndTime": 1762,
"X": 297.0125,
"Y": 191.585434
},
{
"StartTime": 1844,
"EndTime": 1844,
"X": 256.9851,
"Y": 191.390167
},
{
"StartTime": 1926,
"EndTime": 1926,
"X": 216.957672,
"Y": 191.194916
},
{
"StartTime": 2008,
"EndTime": 2008,
"X": 177.069717,
"Y": 191.000336
},
{
"StartTime": 2090,
"EndTime": 2090,
"X": 217.097137,
"Y": 191.1956
},
{
"StartTime": 2172,
"EndTime": 2172,
"X": 257.124573,
"Y": 191.390854
},
{
"StartTime": 2254,
"EndTime": 2254,
"X": 297.152,
"Y": 191.5861
},
{
"StartTime": 2336,
"EndTime": 2336,
"X": 337.179443,
"Y": 191.781372
},
{
"StartTime": 2418,
"EndTime": 2418,
"X": 376.7884,
"Y": 191.974579
},
{
"StartTime": 2500,
"EndTime": 2500,
"X": 336.760956,
"Y": 191.779327
},
{
"StartTime": 2582,
"EndTime": 2582,
"X": 296.733643,
"Y": 191.584076
},
{
"StartTime": 2664,
"EndTime": 2664,
"X": 256.7062,
"Y": 191.388809
},
{
"StartTime": 2746,
"EndTime": 2746,
"X": 216.678772,
"Y": 191.193558
},
{
"StartTime": 2828,
"EndTime": 2828,
"X": 177.348663,
"Y": 191.0017
},
{
"StartTime": 2909,
"EndTime": 2909,
"X": 216.887909,
"Y": 191.19458
},
{
"StartTime": 2991,
"EndTime": 2991,
"X": 256.915344,
"Y": 191.389832
},
{
"StartTime": 3073,
"EndTime": 3073,
"X": 296.942749,
"Y": 191.585083
},
{
"StartTime": 3155,
"EndTime": 3155,
"X": 336.970184,
"Y": 191.78035
},
{
"StartTime": 3201,
"EndTime": 3201,
"X": 376.99762,
"Y": 191.9756
}
]
}]
}

View File

@ -0,0 +1,18 @@
osu file format v14
[General]
StackLeniency: 0.4
Mode: 0
[Difficulty]
CircleSize:4
OverallDifficulty:7
ApproachRate:8
SliderMultiplier:1.6
SliderTickRate:4
[TimingPoints]
369,327.868852459016,4,2,2,32,1,0
[HitObjects]
177,191,369,6,0,L|382:192,7,200

View File

@ -0,0 +1,348 @@
{
"Mappings": [{
"StartTime": 369,
"Objects": [{
"StartTime": 369,
"EndTime": 369,
"X": 127,
"Y": 194
},
{
"StartTime": 450,
"EndTime": 450,
"X": 166.53389,
"Y": 193.8691
},
{
"StartTime": 532,
"EndTime": 532,
"X": 206.555847,
"Y": 193.736572
},
{
"StartTime": 614,
"EndTime": 614,
"X": 246.57782,
"Y": 193.60405
},
{
"StartTime": 696,
"EndTime": 696,
"X": 286.5998,
"Y": 193.471527
},
{
"StartTime": 778,
"EndTime": 778,
"X": 326.621765,
"Y": 193.339
},
{
"StartTime": 860,
"EndTime": 860,
"X": 366.6437,
"Y": 193.206482
},
{
"StartTime": 942,
"EndTime": 942,
"X": 406.66568,
"Y": 193.073959
},
{
"StartTime": 970,
"EndTime": 970,
"X": 420.331726,
"Y": 193.0287
},
{
"StartTime": 997,
"EndTime": 997,
"X": 407.153748,
"Y": 193.072342
},
{
"StartTime": 1079,
"EndTime": 1079,
"X": 367.131775,
"Y": 193.204865
},
{
"StartTime": 1161,
"EndTime": 1161,
"X": 327.1098,
"Y": 193.337387
},
{
"StartTime": 1243,
"EndTime": 1243,
"X": 287.08783,
"Y": 193.46991
},
{
"StartTime": 1325,
"EndTime": 1325,
"X": 247.0659,
"Y": 193.602432
},
{
"StartTime": 1407,
"EndTime": 1407,
"X": 207.043915,
"Y": 193.734955
},
{
"StartTime": 1489,
"EndTime": 1489,
"X": 167.021988,
"Y": 193.867477
},
{
"StartTime": 1571,
"EndTime": 1571,
"X": 127,
"Y": 194
},
{
"StartTime": 1653,
"EndTime": 1653,
"X": 167.021988,
"Y": 193.867477
},
{
"StartTime": 1735,
"EndTime": 1735,
"X": 207.043976,
"Y": 193.734955
},
{
"StartTime": 1817,
"EndTime": 1817,
"X": 247.065887,
"Y": 193.602432
},
{
"StartTime": 1899,
"EndTime": 1899,
"X": 287.08783,
"Y": 193.46991
},
{
"StartTime": 1981,
"EndTime": 1981,
"X": 327.1098,
"Y": 193.337387
},
{
"StartTime": 2062,
"EndTime": 2062,
"X": 366.643738,
"Y": 193.206482
},
{
"StartTime": 2144,
"EndTime": 2144,
"X": 406.665649,
"Y": 193.073959
},
{
"StartTime": 2172,
"EndTime": 2172,
"X": 420.331726,
"Y": 193.0287
},
{
"StartTime": 2199,
"EndTime": 2199,
"X": 407.153748,
"Y": 193.072342
},
{
"StartTime": 2281,
"EndTime": 2281,
"X": 367.1318,
"Y": 193.204865
},
{
"StartTime": 2363,
"EndTime": 2363,
"X": 327.1098,
"Y": 193.337387
},
{
"StartTime": 2445,
"EndTime": 2445,
"X": 287.08783,
"Y": 193.46991
},
{
"StartTime": 2527,
"EndTime": 2527,
"X": 247.065887,
"Y": 193.602432
},
{
"StartTime": 2609,
"EndTime": 2609,
"X": 207.043976,
"Y": 193.734955
},
{
"StartTime": 2691,
"EndTime": 2691,
"X": 167.021988,
"Y": 193.867477
},
{
"StartTime": 2773,
"EndTime": 2773,
"X": 127,
"Y": 194
},
{
"StartTime": 2855,
"EndTime": 2855,
"X": 167.021988,
"Y": 193.867477
},
{
"StartTime": 2937,
"EndTime": 2937,
"X": 207.043976,
"Y": 193.734955
},
{
"StartTime": 3019,
"EndTime": 3019,
"X": 247.065948,
"Y": 193.602432
},
{
"StartTime": 3101,
"EndTime": 3101,
"X": 287.087952,
"Y": 193.46991
},
{
"StartTime": 3183,
"EndTime": 3183,
"X": 327.109772,
"Y": 193.337387
},
{
"StartTime": 3265,
"EndTime": 3265,
"X": 367.131775,
"Y": 193.204865
},
{
"StartTime": 3347,
"EndTime": 3347,
"X": 407.153748,
"Y": 193.072342
},
{
"StartTime": 3374,
"EndTime": 3374,
"X": 420.331726,
"Y": 193.0287
},
{
"StartTime": 3401,
"EndTime": 3401,
"X": 407.153748,
"Y": 193.072342
},
{
"StartTime": 3483,
"EndTime": 3483,
"X": 367.131775,
"Y": 193.204865
},
{
"StartTime": 3565,
"EndTime": 3565,
"X": 327.109772,
"Y": 193.337387
},
{
"StartTime": 3647,
"EndTime": 3647,
"X": 287.087952,
"Y": 193.46991
},
{
"StartTime": 3729,
"EndTime": 3729,
"X": 247.065948,
"Y": 193.602432
},
{
"StartTime": 3811,
"EndTime": 3811,
"X": 207.043976,
"Y": 193.734955
},
{
"StartTime": 3893,
"EndTime": 3893,
"X": 167.021988,
"Y": 193.867477
},
{
"StartTime": 3975,
"EndTime": 3975,
"X": 127,
"Y": 194
},
{
"StartTime": 4057,
"EndTime": 4057,
"X": 167.021988,
"Y": 193.867477
},
{
"StartTime": 4139,
"EndTime": 4139,
"X": 207.043976,
"Y": 193.734955
},
{
"StartTime": 4221,
"EndTime": 4221,
"X": 247.065948,
"Y": 193.602432
},
{
"StartTime": 4303,
"EndTime": 4303,
"X": 287.087952,
"Y": 193.46991
},
{
"StartTime": 4385,
"EndTime": 4385,
"X": 327.109772,
"Y": 193.337387
},
{
"StartTime": 4467,
"EndTime": 4467,
"X": 367.131775,
"Y": 193.204865
},
{
"StartTime": 4540,
"EndTime": 4540,
"X": 420.331726,
"Y": 193.0287
},
{
"StartTime": 4549,
"EndTime": 4549,
"X": 407.153748,
"Y": 193.072342
}
]
}]
}

View File

@ -0,0 +1,19 @@
osu file format v14
[General]
StackLeniency: 0.4
Mode: 0
[Difficulty]
CircleSize:4
OverallDifficulty:7
ApproachRate:8
SliderMultiplier:1.6
SliderTickRate:4
[TimingPoints]
369,327.868852459016,4,2,2,32,1,0
[HitObjects]
// A slider with an un-even amount of ticks
127,194,369,6,0,L|429:193,7,293.333333333333

View File

@ -0,0 +1,116 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Game.Rulesets.Objects;
namespace osu.Game.Tests.Beatmaps
{
[TestFixture]
public class SliderEventGenerationTest
{
private const double start_time = 0;
private const double span_duration = 1000;
[Test]
public void TestSingleSpan()
{
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray();
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
Assert.That(events[0].Time, Is.EqualTo(start_time));
Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[1].Time, Is.EqualTo(span_duration / 2));
Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tail));
Assert.That(events[3].Time, Is.EqualTo(span_duration));
}
[Test]
public void TestRepeat()
{
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray();
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
Assert.That(events[0].Time, Is.EqualTo(start_time));
Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[1].Time, Is.EqualTo(span_duration / 2));
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.Repeat));
Assert.That(events[2].Time, Is.EqualTo(span_duration));
Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[3].Time, Is.EqualTo(span_duration + span_duration / 2));
Assert.That(events[5].Type, Is.EqualTo(SliderEventType.Tail));
Assert.That(events[5].Time, Is.EqualTo(2 * span_duration));
}
[Test]
public void TestNonEvenTicks()
{
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray();
Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head));
Assert.That(events[0].Time, Is.EqualTo(start_time));
Assert.That(events[1].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[1].Time, Is.EqualTo(300));
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[2].Time, Is.EqualTo(600));
Assert.That(events[3].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[3].Time, Is.EqualTo(900));
Assert.That(events[4].Type, Is.EqualTo(SliderEventType.Repeat));
Assert.That(events[4].Time, Is.EqualTo(span_duration));
Assert.That(events[5].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[5].Time, Is.EqualTo(1100));
Assert.That(events[6].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[6].Time, Is.EqualTo(1400));
Assert.That(events[7].Type, Is.EqualTo(SliderEventType.Tick));
Assert.That(events[7].Time, Is.EqualTo(1700));
Assert.That(events[9].Type, Is.EqualTo(SliderEventType.Tail));
Assert.That(events[9].Time, Is.EqualTo(2 * span_duration));
}
[Test]
public void TestLegacyLastTickOffset()
{
var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray();
Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick));
Assert.That(events[2].Time, Is.EqualTo(900));
}
[Test]
public void TestMinimumTickDistance()
{
const double velocity = 5;
const double min_distance = velocity * 10;
var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray();
Assert.Multiple(() =>
{
int tickIndex = -1;
while (++tickIndex < events.Length)
{
if (events[tickIndex].Type != SliderEventType.Tick)
continue;
Assert.That(events[tickIndex].Time, Is.LessThan(span_duration - min_distance).Or.GreaterThan(span_duration + min_distance));
}
});
}
}
}

View File

@ -3,13 +3,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osuTK;
namespace osu.Game.Rulesets.Objects
{
public static class SliderEventGenerator
{
public static IEnumerable<SliderEventDescriptor> Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, double? legacyLastTickOffset)
public static IEnumerable<SliderEventDescriptor> Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount,
double? legacyLastTickOffset)
{
// A very lenient maximum length of a slider for ticks to be generated.
// This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage.
@ -36,24 +38,17 @@ namespace osu.Game.Rulesets.Objects
var spanStartTime = startTime + span * spanDuration;
var reversed = span % 2 == 1;
for (var d = tickDistance; d <= length; d += tickDistance)
var ticks = generateTicks(span, spanStartTime, spanDuration, reversed, length, tickDistance, minDistanceFromEnd);
if (reversed)
{
if (d >= length - minDistanceFromEnd)
break;
var pathProgress = d / length;
var timeProgress = reversed ? 1 - pathProgress : pathProgress;
yield return new SliderEventDescriptor
{
Type = SliderEventType.Tick,
SpanIndex = span,
SpanStartTime = spanStartTime,
Time = spanStartTime + timeProgress * spanDuration,
PathProgress = pathProgress,
};
// For repeat spans, ticks are returned in reverse-StartTime order, which is undesirable for some rulesets
ticks = ticks.Reverse();
}
foreach (var e in ticks)
yield return e;
if (span < spanCount - 1)
{
yield return new SliderEventDescriptor
@ -103,6 +98,40 @@ namespace osu.Game.Rulesets.Objects
PathProgress = spanCount % 2,
};
}
/// <summary>
/// Generates the ticks for a span of the slider.
/// </summary>
/// <param name="spanIndex">The span index.</param>
/// <param name="spanStartTime">The start time of the span.</param>
/// <param name="spanDuration">The duration of the span.</param>
/// <param name="reversed">Whether the span is reversed.</param>
/// <param name="length">The length of the path.</param>
/// <param name="tickDistance">The distance between each tick.</param>
/// <param name="minDistanceFromEnd">The distance from the end of the path at which ticks are not allowed to be added.</param>
/// <returns>A <see cref="SliderEventDescriptor"/> for each tick. If <paramref name="reversed"/> is true, the ticks will be returned in reverse-StartTime order.</returns>
private static IEnumerable<SliderEventDescriptor> generateTicks(int spanIndex, double spanStartTime, double spanDuration, bool reversed, double length, double tickDistance,
double minDistanceFromEnd)
{
for (var d = tickDistance; d <= length; d += tickDistance)
{
if (d >= length - minDistanceFromEnd)
break;
// Always generate ticks from the start of the path rather than the span to ensure that ticks in repeat spans are positioned identically to those in non-repeat spans
var pathProgress = d / length;
var timeProgress = reversed ? 1 - pathProgress : pathProgress;
yield return new SliderEventDescriptor
{
Type = SliderEventType.Tick,
SpanIndex = spanIndex,
SpanStartTime = spanStartTime,
Time = spanStartTime + timeProgress * spanDuration,
PathProgress = pathProgress,
};
}
}
}
/// <summary>