Skip to content

Commit bf972a0

Browse files
authored
Merge pull request #25409 from Tom94/bspline-sliders
Add free-hand drawing of sliders to the editor
2 parents d83b2e2 + 4b2d8aa commit bf972a0

File tree

59 files changed

+780
-362
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+780
-362
lines changed

osu.Android.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
1111
</PropertyGroup>
1212
<ItemGroup>
13-
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.1111.0" />
13+
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.1121.0" />
1414
</ItemGroup>
1515
<PropertyGroup>
1616
<!-- Fody does not handle Android build well, and warns when unchanged.

osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public void TestUpdateFromHitObject()
140140

141141
AddStep("update hit object path", () =>
142142
{
143-
hitObject.Path = new SliderPath(PathType.PerfectCurve, new[]
143+
hitObject.Path = new SliderPath(PathType.PERFECT_CURVE, new[]
144144
{
145145
Vector2.Zero,
146146
new Vector2(100, 100),
@@ -190,16 +190,16 @@ public void TestDeleteVertex()
190190
[Test]
191191
public void TestVertexResampling()
192192
{
193-
addBlueprintStep(100, 100, new SliderPath(PathType.PerfectCurve, new[]
193+
addBlueprintStep(100, 100, new SliderPath(PathType.PERFECT_CURVE, new[]
194194
{
195195
Vector2.Zero,
196196
new Vector2(100, 100),
197197
new Vector2(50, 200),
198198
}), 0.5);
199199
AddAssert("1 vertex per 1 nested HO", () => getVertices().Count == hitObject.NestedHitObjects.Count);
200-
AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type == PathType.PerfectCurve);
200+
AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type == PathType.PERFECT_CURVE);
201201
addAddVertexSteps(150, 150);
202-
AddAssert("slider path change to linear", () => hitObject.Path.ControlPoints[0].Type == PathType.Linear);
202+
AddAssert("slider path change to linear", () => hitObject.Path.ControlPoints[0].Type == PathType.LINEAR);
203203
}
204204

205205
private void addBlueprintStep(double time, float x, SliderPath sliderPath, double velocity) => AddStep("add selection blueprint", () =>

osu.Game.Rulesets.Catch.Tests/JuiceStreamPathTest.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ public void TestRandomConvertFromSliderPath(double velocity)
154154
} while (rng.Next(2) != 0);
155155

156156
int length = sliderPath.ControlPoints.Count - start + 1;
157-
sliderPath.ControlPoints[start].Type = length <= 2 ? PathType.Linear : length == 3 ? PathType.PerfectCurve : PathType.Bezier;
157+
sliderPath.ControlPoints[start].Type = length <= 2 ? PathType.LINEAR : length == 3 ? PathType.PERFECT_CURVE : PathType.BEZIER;
158158
} while (rng.Next(3) != 0);
159159

160160
if (rng.Next(5) == 0)
@@ -215,7 +215,7 @@ public void TestRandomConvertToSliderPath()
215215

216216
foreach (var point in sliderPath.ControlPoints)
217217
{
218-
Assert.That(point.Type, Is.EqualTo(PathType.Linear).Or.Null);
218+
Assert.That(point.Type, Is.EqualTo(PathType.LINEAR).Or.Null);
219219
Assert.That(sliderStartY + point.Position.Y, Is.InRange(0, JuiceStreamPath.OSU_PLAYFIELD_HEIGHT));
220220
}
221221

osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public void TestJuiceStream(bool shouldMiss)
3535
var stream = new JuiceStream
3636
{
3737
StartTime = 1000,
38-
Path = new SliderPath(PathType.Linear, new[]
38+
Path = new SliderPath(PathType.LINEAR, new[]
3939
{
4040
Vector2.Zero,
4141
new Vector2(100, 0),

osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public void TestModRelax() => CreateModTest(new ModTestData
5151
{
5252
X = CatchPlayfield.CENTER_X,
5353
StartTime = 3000,
54-
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 })
54+
Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, Vector2.UnitY * 200 })
5555
}
5656
}
5757
}

osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
3434
beatmap.HitObjects.Add(new JuiceStream
3535
{
3636
X = CatchPlayfield.CENTER_X - width / 2,
37-
Path = new SliderPath(PathType.Linear, new[]
37+
Path = new SliderPath(PathType.LINEAR, new[]
3838
{
3939
Vector2.Zero,
4040
new Vector2(width, 0)

osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public void TestJuiceStream()
3131
new JuiceStream
3232
{
3333
StartTime = 1000,
34-
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(0, -192) }),
34+
Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(0, -192) }),
3535
X = CatchPlayfield.WIDTH / 2
3636
}
3737
}

osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ private void spawnJuiceStream(bool hit = false)
126126
{
127127
X = xCoords,
128128
StartTime = playfieldTime + 1000,
129-
Path = new SliderPath(PathType.Linear, new[]
129+
Path = new SliderPath(PathType.LINEAR, new[]
130130
{
131131
Vector2.Zero,
132132
new Vector2(0, 200)

osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public void TestJuiceStreamEndingCombo()
3232
new JuiceStream
3333
{
3434
X = CatchPlayfield.CENTER_X,
35-
Path = new SliderPath(PathType.Linear, new[]
35+
Path = new SliderPath(PathType.LINEAR, new[]
3636
{
3737
Vector2.Zero,
3838
new Vector2(0, 100)

osu.Game.Rulesets.Catch/Edit/Blueprints/Components/EditablePath.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public void InitializeFromHitObject(JuiceStream hitObject)
7474
path.ConvertFromSliderPath(sliderPath, hitObject.Velocity);
7575

7676
// If the original slider path has non-linear type segments, resample the vertices at nested hit object times to reduce the number of vertices.
77-
if (sliderPath.ControlPoints.Any(p => p.Type != null && p.Type != PathType.Linear))
77+
if (sliderPath.ControlPoints.Any(p => p.Type != null && p.Type != PathType.LINEAR))
7878
{
7979
path.ResampleVertices(hitObject.NestedHitObjects
8080
.Skip(1).TakeWhile(h => !(h is Fruit)) // Only droplets in the first span are used.

osu.Game.Rulesets.Catch/Objects/JuiceStreamPath.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ public void ConvertToSliderPath(SliderPath sliderPath, float sliderStartY, doubl
236236

237237
for (int i = 1; i < vertices.Count; i++)
238238
{
239-
sliderPath.ControlPoints[^1].Type = PathType.Linear;
239+
sliderPath.ControlPoints[^1].Type = PathType.LINEAR;
240240

241241
float deltaX = vertices[i].X - lastPosition.X;
242242
double length = (vertices[i].Time - currentTime) * velocity;

osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckOffscreenObjectsTest.cs

+6-6
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public void TestSliderInCenter()
107107
Position = new Vector2(420, 240),
108108
Path = new SliderPath(new[]
109109
{
110-
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
110+
new PathControlPoint(new Vector2(0, 0), PathType.LINEAR),
111111
new PathControlPoint(new Vector2(-100, 0))
112112
}),
113113
}
@@ -128,7 +128,7 @@ public void TestSliderNearEdge()
128128
Position = playfield_centre,
129129
Path = new SliderPath(new[]
130130
{
131-
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
131+
new PathControlPoint(new Vector2(0, 0), PathType.LINEAR),
132132
new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5))
133133
}),
134134
}
@@ -149,7 +149,7 @@ public void TestSliderNearEdgeStackedOffscreen()
149149
Position = playfield_centre,
150150
Path = new SliderPath(new[]
151151
{
152-
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
152+
new PathControlPoint(new Vector2(0, 0), PathType.LINEAR),
153153
new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5))
154154
}),
155155
StackHeight = 5
@@ -171,7 +171,7 @@ public void TestSliderOffscreenStart()
171171
Position = new Vector2(0, 0),
172172
Path = new SliderPath(new[]
173173
{
174-
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
174+
new PathControlPoint(new Vector2(0, 0), PathType.LINEAR),
175175
new PathControlPoint(playfield_centre)
176176
}),
177177
}
@@ -192,7 +192,7 @@ public void TestSliderOffscreenEnd()
192192
Position = playfield_centre,
193193
Path = new SliderPath(new[]
194194
{
195-
new PathControlPoint(new Vector2(0, 0), PathType.Linear),
195+
new PathControlPoint(new Vector2(0, 0), PathType.LINEAR),
196196
new PathControlPoint(-playfield_centre)
197197
}),
198198
}
@@ -214,7 +214,7 @@ public void TestSliderOffscreenPath()
214214
Path = new SliderPath(new[]
215215
{
216216
// Circular arc shoots over the top of the screen.
217-
new PathControlPoint(new Vector2(0, 0), PathType.PerfectCurve),
217+
new PathControlPoint(new Vector2(0, 0), PathType.PERFECT_CURVE),
218218
new PathControlPoint(new Vector2(-100, -200)),
219219
new PathControlPoint(new Vector2(100, -200))
220220
}),

osu.Game.Rulesets.Osu.Tests/Editor/TestSceneObjectMerging.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public void TestSimpleMerge()
3939
mergeSelection();
4040

4141
AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor(
42-
(pos: circle1.Position, pathType: PathType.Linear),
42+
(pos: circle1.Position, pathType: PathType.LINEAR),
4343
(pos: circle2.Position, pathType: null)));
4444

4545
AddStep("undo", () => Editor.Undo());
@@ -73,11 +73,11 @@ public void TestMergeCircleSlider()
7373

7474
var controlPoints = slider.Path.ControlPoints;
7575
(Vector2, PathType?)[] args = new (Vector2, PathType?)[controlPoints.Count + 2];
76-
args[0] = (circle1.Position, PathType.Linear);
76+
args[0] = (circle1.Position, PathType.LINEAR);
7777

7878
for (int i = 0; i < controlPoints.Count; i++)
7979
{
80-
args[i + 1] = (controlPoints[i].Position + slider.Position, i == controlPoints.Count - 1 ? PathType.Linear : controlPoints[i].Type);
80+
args[i + 1] = (controlPoints[i].Position + slider.Position, i == controlPoints.Count - 1 ? PathType.LINEAR : controlPoints[i].Type);
8181
}
8282

8383
args[^1] = (circle2.Position, null);
@@ -172,7 +172,7 @@ public void TestNonMerge()
172172
mergeSelection();
173173

174174
AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor(
175-
(pos: circle1.Position, pathType: PathType.Linear),
175+
(pos: circle1.Position, pathType: PathType.LINEAR),
176176
(pos: circle2.Position, pathType: null)));
177177

178178
AddAssert("samples exist", sliderSampleExist);
@@ -227,7 +227,7 @@ public void TestSameStartTimeMerge()
227227
mergeSelection();
228228

229229
AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor(
230-
(pos: circle1.Position, pathType: PathType.Linear),
230+
(pos: circle1.Position, pathType: PathType.LINEAR),
231231
(pos: circle2.Position, pathType: null)));
232232
}
233233

osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorSelectInvalidPath.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public void TestSelectDoesNotModify()
2525

2626
PathControlPoint[] points =
2727
{
28-
new PathControlPoint(new Vector2(0), PathType.PerfectCurve),
28+
new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE),
2929
new PathControlPoint(new Vector2(-100, 0)),
3030
new PathControlPoint(new Vector2(100, 20))
3131
};

osu.Game.Rulesets.Osu.Tests/Editor/TestScenePathControlPointVisualiser.cs

+43-19
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public void TestPerfectCurveTooManyPoints()
5252
{
5353
createVisualiser(true);
5454

55-
addControlPointStep(new Vector2(200), PathType.Bezier);
55+
addControlPointStep(new Vector2(200), PathType.BEZIER);
5656
addControlPointStep(new Vector2(300));
5757
addControlPointStep(new Vector2(500, 300));
5858
addControlPointStep(new Vector2(700, 200));
@@ -63,17 +63,17 @@ public void TestPerfectCurveTooManyPoints()
6363
AddStep("select control point", () => visualiser.Pieces[1].IsSelected.Value = true);
6464
addContextMenuItemStep("Perfect curve");
6565

66-
assertControlPointPathType(0, PathType.Bezier);
67-
assertControlPointPathType(1, PathType.PerfectCurve);
68-
assertControlPointPathType(3, PathType.Bezier);
66+
assertControlPointPathType(0, PathType.BEZIER);
67+
assertControlPointPathType(1, PathType.PERFECT_CURVE);
68+
assertControlPointPathType(3, PathType.BEZIER);
6969
}
7070

7171
[Test]
7272
public void TestPerfectCurveLastThreePoints()
7373
{
7474
createVisualiser(true);
7575

76-
addControlPointStep(new Vector2(200), PathType.Bezier);
76+
addControlPointStep(new Vector2(200), PathType.BEZIER);
7777
addControlPointStep(new Vector2(300));
7878
addControlPointStep(new Vector2(500, 300));
7979
addControlPointStep(new Vector2(700, 200));
@@ -83,8 +83,8 @@ public void TestPerfectCurveLastThreePoints()
8383
AddStep("select control point", () => visualiser.Pieces[2].IsSelected.Value = true);
8484
addContextMenuItemStep("Perfect curve");
8585

86-
assertControlPointPathType(0, PathType.Bezier);
87-
assertControlPointPathType(2, PathType.PerfectCurve);
86+
assertControlPointPathType(0, PathType.BEZIER);
87+
assertControlPointPathType(2, PathType.PERFECT_CURVE);
8888
assertControlPointPathType(4, null);
8989
}
9090

@@ -93,7 +93,7 @@ public void TestPerfectCurveLastTwoPoints()
9393
{
9494
createVisualiser(true);
9595

96-
addControlPointStep(new Vector2(200), PathType.Bezier);
96+
addControlPointStep(new Vector2(200), PathType.BEZIER);
9797
addControlPointStep(new Vector2(300));
9898
addControlPointStep(new Vector2(500, 300));
9999
addControlPointStep(new Vector2(700, 200));
@@ -103,7 +103,7 @@ public void TestPerfectCurveLastTwoPoints()
103103
AddStep("select control point", () => visualiser.Pieces[3].IsSelected.Value = true);
104104
addContextMenuItemStep("Perfect curve");
105105

106-
assertControlPointPathType(0, PathType.Bezier);
106+
assertControlPointPathType(0, PathType.BEZIER);
107107
AddAssert("point 3 is not inherited", () => slider.Path.ControlPoints[3].Type != null);
108108
}
109109

@@ -112,7 +112,7 @@ public void TestPerfectCurveTooManyPointsLinear()
112112
{
113113
createVisualiser(true);
114114

115-
addControlPointStep(new Vector2(200), PathType.Linear);
115+
addControlPointStep(new Vector2(200), PathType.LINEAR);
116116
addControlPointStep(new Vector2(300));
117117
addControlPointStep(new Vector2(500, 300));
118118
addControlPointStep(new Vector2(700, 200));
@@ -123,31 +123,55 @@ public void TestPerfectCurveTooManyPointsLinear()
123123
AddStep("select control point", () => visualiser.Pieces[1].IsSelected.Value = true);
124124
addContextMenuItemStep("Perfect curve");
125125

126-
assertControlPointPathType(0, PathType.Linear);
127-
assertControlPointPathType(1, PathType.PerfectCurve);
128-
assertControlPointPathType(3, PathType.Linear);
126+
assertControlPointPathType(0, PathType.LINEAR);
127+
assertControlPointPathType(1, PathType.PERFECT_CURVE);
128+
assertControlPointPathType(3, PathType.LINEAR);
129129
}
130130

131131
[Test]
132132
public void TestPerfectCurveChangeToBezier()
133133
{
134134
createVisualiser(true);
135135

136-
addControlPointStep(new Vector2(200), PathType.Bezier);
137-
addControlPointStep(new Vector2(300), PathType.PerfectCurve);
136+
addControlPointStep(new Vector2(200), PathType.BEZIER);
137+
addControlPointStep(new Vector2(300), PathType.PERFECT_CURVE);
138138
addControlPointStep(new Vector2(500, 300));
139-
addControlPointStep(new Vector2(700, 200), PathType.Bezier);
139+
addControlPointStep(new Vector2(700, 200), PathType.BEZIER);
140140
addControlPointStep(new Vector2(500, 100));
141141

142142
moveMouseToControlPoint(3);
143143
AddStep("select control point", () => visualiser.Pieces[3].IsSelected.Value = true);
144144
addContextMenuItemStep("Inherit");
145145

146-
assertControlPointPathType(0, PathType.Bezier);
147-
assertControlPointPathType(1, PathType.Bezier);
146+
assertControlPointPathType(0, PathType.BEZIER);
147+
assertControlPointPathType(1, PathType.BEZIER);
148148
assertControlPointPathType(3, null);
149149
}
150150

151+
[Test]
152+
public void TestCatmullAvailableIffSelectionContainsCatmull()
153+
{
154+
createVisualiser(true);
155+
156+
addControlPointStep(new Vector2(200), PathType.CATMULL);
157+
addControlPointStep(new Vector2(300));
158+
addControlPointStep(new Vector2(500, 300));
159+
addControlPointStep(new Vector2(700, 200));
160+
addControlPointStep(new Vector2(500, 100));
161+
162+
moveMouseToControlPoint(2);
163+
AddStep("select first and third control point", () =>
164+
{
165+
visualiser.Pieces[0].IsSelected.Value = true;
166+
visualiser.Pieces[2].IsSelected.Value = true;
167+
});
168+
addContextMenuItemStep("Catmull");
169+
170+
assertControlPointPathType(0, PathType.CATMULL);
171+
assertControlPointPathType(2, PathType.CATMULL);
172+
assertControlPointPathType(4, null);
173+
}
174+
151175
private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser<Slider>(slider, allowSelection)
152176
{
153177
Anchor = Anchor.Centre,
@@ -158,7 +182,7 @@ public void TestPerfectCurveChangeToBezier()
158182

159183
private void addControlPointStep(Vector2 position, PathType? type)
160184
{
161-
AddStep($"add {type} control point at {position}", () =>
185+
AddStep($"add {type?.Type} control point at {position}", () =>
162186
{
163187
slider.Path.ControlPoints.Add(new PathControlPoint(position, type));
164188
});

0 commit comments

Comments
 (0)