-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
SliderEventGenerator.cs
183 lines (156 loc) · 7.68 KB
/
SliderEventGenerator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// 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.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace osu.Game.Rulesets.Objects
{
public static class SliderEventGenerator
{
// ReSharper disable once MethodOverloadWithOptionalParameter
public static IEnumerable<SliderEventDescriptor> Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount,
double? legacyLastTickOffset, CancellationToken cancellationToken = default)
{
// 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.
const double max_length = 100000;
double length = Math.Min(max_length, totalDistance);
tickDistance = Math.Clamp(tickDistance, 0, length);
double minDistanceFromEnd = velocity * 10;
yield return new SliderEventDescriptor
{
Type = SliderEventType.Head,
SpanIndex = 0,
SpanStartTime = startTime,
Time = startTime,
PathProgress = 0,
};
if (tickDistance != 0)
{
for (int span = 0; span < spanCount; span++)
{
double spanStartTime = startTime + span * spanDuration;
bool reversed = span % 2 == 1;
var ticks = generateTicks(span, spanStartTime, spanDuration, reversed, length, tickDistance, minDistanceFromEnd, cancellationToken);
if (reversed)
{
// 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
{
Type = SliderEventType.Repeat,
SpanIndex = span,
SpanStartTime = startTime + span * spanDuration,
Time = spanStartTime + spanDuration,
PathProgress = (span + 1) % 2,
};
}
}
}
double totalDuration = spanCount * spanDuration;
// Okay, I'll level with you. I made a mistake. It was 2007.
// Times were simpler. osu! was but in its infancy and sliders were a new concept.
// A hack was made, which has unfortunately lived through until this day.
//
// This legacy tick is used for some calculations and judgements where audio output is not required.
// Generally we are keeping this around just for difficulty compatibility.
// Optimistically we do not want to ever use this for anything user-facing going forwards.
int finalSpanIndex = spanCount - 1;
double finalSpanStartTime = startTime + finalSpanIndex * spanDuration;
double finalSpanEndTime = Math.Max(startTime + totalDuration / 2, (finalSpanStartTime + spanDuration) - (legacyLastTickOffset ?? 0));
double finalProgress = (finalSpanEndTime - finalSpanStartTime) / spanDuration;
if (spanCount % 2 == 0) finalProgress = 1 - finalProgress;
yield return new SliderEventDescriptor
{
Type = SliderEventType.LegacyLastTick,
SpanIndex = finalSpanIndex,
SpanStartTime = finalSpanStartTime,
Time = finalSpanEndTime,
PathProgress = finalProgress,
};
yield return new SliderEventDescriptor
{
Type = SliderEventType.Tail,
SpanIndex = finalSpanIndex,
SpanStartTime = startTime + (spanCount - 1) * spanDuration,
Time = startTime + totalDuration,
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>
/// <param name="cancellationToken">The cancellation token.</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, CancellationToken cancellationToken = default)
{
for (double d = tickDistance; d <= length; d += tickDistance)
{
cancellationToken.ThrowIfCancellationRequested();
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
double pathProgress = d / length;
double timeProgress = reversed ? 1 - pathProgress : pathProgress;
yield return new SliderEventDescriptor
{
Type = SliderEventType.Tick,
SpanIndex = spanIndex,
SpanStartTime = spanStartTime,
Time = spanStartTime + timeProgress * spanDuration,
PathProgress = pathProgress,
};
}
}
}
/// <summary>
/// Describes a point in time on a slider given special meaning.
/// Should be used by rulesets to visualise the slider.
/// </summary>
public struct SliderEventDescriptor
{
/// <summary>
/// The type of event.
/// </summary>
public SliderEventType Type;
/// <summary>
/// The time of this event.
/// </summary>
public double Time;
/// <summary>
/// The zero-based index of the span. In the case of repeat sliders, this will increase after each <see cref="SliderEventType.Repeat"/>.
/// </summary>
public int SpanIndex;
/// <summary>
/// The time at which the contained <see cref="SpanIndex"/> begins.
/// </summary>
public double SpanStartTime;
/// <summary>
/// The progress along the slider's <see cref="SliderPath"/> at which this event occurs.
/// </summary>
public double PathProgress;
}
public enum SliderEventType
{
Tick,
LegacyLastTick,
Head,
Tail,
Repeat
}
}