1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 18:07:23 +08:00

Generalize Bezier curves to BSplines of Nth degree

This commit is contained in:
Thomas Müller-Höhne 2023-11-08 19:43:54 +09:00 committed by cs
parent 9df8fc14e7
commit 926636cc03
54 changed files with 372 additions and 305 deletions

View File

@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
AddStep("update hit object path", () => AddStep("update hit object path", () =>
{ {
hitObject.Path = new SliderPath(PathType.PerfectCurve, new[] hitObject.Path = new SliderPath(PathType.PERFECTCURVE, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(100, 100), new Vector2(100, 100),
@ -190,16 +190,16 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
[Test] [Test]
public void TestVertexResampling() public void TestVertexResampling()
{ {
addBlueprintStep(100, 100, new SliderPath(PathType.PerfectCurve, new[] addBlueprintStep(100, 100, new SliderPath(PathType.PERFECTCURVE, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(100, 100), new Vector2(100, 100),
new Vector2(50, 200), new Vector2(50, 200),
}), 0.5); }), 0.5);
AddAssert("1 vertex per 1 nested HO", () => getVertices().Count == hitObject.NestedHitObjects.Count); AddAssert("1 vertex per 1 nested HO", () => getVertices().Count == hitObject.NestedHitObjects.Count);
AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type == PathType.PerfectCurve); AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type == PathType.PERFECTCURVE);
addAddVertexSteps(150, 150); addAddVertexSteps(150, 150);
AddAssert("slider path change to linear", () => hitObject.Path.ControlPoints[0].Type == PathType.Linear); AddAssert("slider path change to linear", () => hitObject.Path.ControlPoints[0].Type == PathType.LINEAR);
} }
private void addBlueprintStep(double time, float x, SliderPath sliderPath, double velocity) => AddStep("add selection blueprint", () => private void addBlueprintStep(double time, float x, SliderPath sliderPath, double velocity) => AddStep("add selection blueprint", () =>

View File

@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Catch.Tests
} while (rng.Next(2) != 0); } while (rng.Next(2) != 0);
int length = sliderPath.ControlPoints.Count - start + 1; int length = sliderPath.ControlPoints.Count - start + 1;
sliderPath.ControlPoints[start].Type = length <= 2 ? PathType.Linear : length == 3 ? PathType.PerfectCurve : PathType.Bezier; sliderPath.ControlPoints[start].Type = length <= 2 ? PathType.LINEAR : length == 3 ? PathType.PERFECTCURVE : PathType.BEZIER;
} while (rng.Next(3) != 0); } while (rng.Next(3) != 0);
if (rng.Next(5) == 0) if (rng.Next(5) == 0)
@ -215,7 +215,7 @@ namespace osu.Game.Rulesets.Catch.Tests
foreach (var point in sliderPath.ControlPoints) foreach (var point in sliderPath.ControlPoints)
{ {
Assert.That(point.Type, Is.EqualTo(PathType.Linear).Or.Null); Assert.That(point.Type, Is.EqualTo(PathType.LINEAR).Or.Null);
Assert.That(sliderStartY + point.Position.Y, Is.InRange(0, JuiceStreamPath.OSU_PLAYFIELD_HEIGHT)); Assert.That(sliderStartY + point.Position.Y, Is.InRange(0, JuiceStreamPath.OSU_PLAYFIELD_HEIGHT));
} }

View File

@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
var stream = new JuiceStream var stream = new JuiceStream
{ {
StartTime = 1000, StartTime = 1000,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(100, 0), new Vector2(100, 0),

View File

@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
{ {
X = CatchPlayfield.CENTER_X, X = CatchPlayfield.CENTER_X,
StartTime = 3000, StartTime = 3000,
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }) Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, Vector2.UnitY * 200 })
} }
} }
} }

View File

@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Tests
beatmap.HitObjects.Add(new JuiceStream beatmap.HitObjects.Add(new JuiceStream
{ {
X = CatchPlayfield.CENTER_X - width / 2, X = CatchPlayfield.CENTER_X - width / 2,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(width, 0) new Vector2(width, 0)

View File

@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Tests
new JuiceStream new JuiceStream
{ {
StartTime = 1000, StartTime = 1000,
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(0, -192) }), Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(0, -192) }),
X = CatchPlayfield.WIDTH / 2 X = CatchPlayfield.WIDTH / 2
} }
} }

View File

@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{ {
X = xCoords, X = xCoords,
StartTime = playfieldTime + 1000, StartTime = playfieldTime + 1000,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(0, 200) new Vector2(0, 200)

View File

@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Tests
new JuiceStream new JuiceStream
{ {
X = CatchPlayfield.CENTER_X, X = CatchPlayfield.CENTER_X,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(0, 100) new Vector2(0, 100)

View File

@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
path.ConvertFromSliderPath(sliderPath, hitObject.Velocity); path.ConvertFromSliderPath(sliderPath, hitObject.Velocity);
// If the original slider path has non-linear type segments, resample the vertices at nested hit object times to reduce the number of vertices. // If the original slider path has non-linear type segments, resample the vertices at nested hit object times to reduce the number of vertices.
if (sliderPath.ControlPoints.Any(p => p.Type != null && p.Type != PathType.Linear)) if (sliderPath.ControlPoints.Any(p => p.Type != null && p.Type != PathType.LINEAR))
{ {
path.ResampleVertices(hitObject.NestedHitObjects path.ResampleVertices(hitObject.NestedHitObjects
.Skip(1).TakeWhile(h => !(h is Fruit)) // Only droplets in the first span are used. .Skip(1).TakeWhile(h => !(h is Fruit)) // Only droplets in the first span are used.

View File

@ -236,7 +236,7 @@ namespace osu.Game.Rulesets.Catch.Objects
for (int i = 1; i < vertices.Count; i++) for (int i = 1; i < vertices.Count; i++)
{ {
sliderPath.ControlPoints[^1].Type = PathType.Linear; sliderPath.ControlPoints[^1].Type = PathType.LINEAR;
float deltaX = vertices[i].X - lastPosition.X; float deltaX = vertices[i].X - lastPosition.X;
double length = (vertices[i].Time - currentTime) * velocity; double length = (vertices[i].Time - currentTime) * velocity;

View File

@ -107,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
Position = new Vector2(420, 240), Position = new Vector2(420, 240),
Path = new SliderPath(new[] Path = new SliderPath(new[]
{ {
new PathControlPoint(new Vector2(0, 0), PathType.Linear), new PathControlPoint(new Vector2(0, 0), PathType.LINEAR),
new PathControlPoint(new Vector2(-100, 0)) new PathControlPoint(new Vector2(-100, 0))
}), }),
} }
@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
Position = playfield_centre, Position = playfield_centre,
Path = new SliderPath(new[] Path = new SliderPath(new[]
{ {
new PathControlPoint(new Vector2(0, 0), PathType.Linear), new PathControlPoint(new Vector2(0, 0), PathType.LINEAR),
new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5)) new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5))
}), }),
} }
@ -149,7 +149,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
Position = playfield_centre, Position = playfield_centre,
Path = new SliderPath(new[] Path = new SliderPath(new[]
{ {
new PathControlPoint(new Vector2(0, 0), PathType.Linear), new PathControlPoint(new Vector2(0, 0), PathType.LINEAR),
new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5)) new PathControlPoint(new Vector2(0, -playfield_centre.Y + 5))
}), }),
StackHeight = 5 StackHeight = 5
@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
Position = new Vector2(0, 0), Position = new Vector2(0, 0),
Path = new SliderPath(new[] Path = new SliderPath(new[]
{ {
new PathControlPoint(new Vector2(0, 0), PathType.Linear), new PathControlPoint(new Vector2(0, 0), PathType.LINEAR),
new PathControlPoint(playfield_centre) new PathControlPoint(playfield_centre)
}), }),
} }
@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
Position = playfield_centre, Position = playfield_centre,
Path = new SliderPath(new[] Path = new SliderPath(new[]
{ {
new PathControlPoint(new Vector2(0, 0), PathType.Linear), new PathControlPoint(new Vector2(0, 0), PathType.LINEAR),
new PathControlPoint(-playfield_centre) new PathControlPoint(-playfield_centre)
}), }),
} }
@ -214,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks
Path = new SliderPath(new[] Path = new SliderPath(new[]
{ {
// Circular arc shoots over the top of the screen. // Circular arc shoots over the top of the screen.
new PathControlPoint(new Vector2(0, 0), PathType.PerfectCurve), new PathControlPoint(new Vector2(0, 0), PathType.PERFECTCURVE),
new PathControlPoint(new Vector2(-100, -200)), new PathControlPoint(new Vector2(-100, -200)),
new PathControlPoint(new Vector2(100, -200)) new PathControlPoint(new Vector2(100, -200))
}), }),

View File

@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
mergeSelection(); mergeSelection();
AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor( AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor(
(pos: circle1.Position, pathType: PathType.Linear), (pos: circle1.Position, pathType: PathType.LINEAR),
(pos: circle2.Position, pathType: null))); (pos: circle2.Position, pathType: null)));
AddStep("undo", () => Editor.Undo()); AddStep("undo", () => Editor.Undo());
@ -73,11 +73,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
var controlPoints = slider.Path.ControlPoints; var controlPoints = slider.Path.ControlPoints;
(Vector2, PathType?)[] args = new (Vector2, PathType?)[controlPoints.Count + 2]; (Vector2, PathType?)[] args = new (Vector2, PathType?)[controlPoints.Count + 2];
args[0] = (circle1.Position, PathType.Linear); args[0] = (circle1.Position, PathType.LINEAR);
for (int i = 0; i < controlPoints.Count; i++) for (int i = 0; i < controlPoints.Count; i++)
{ {
args[i + 1] = (controlPoints[i].Position + slider.Position, i == controlPoints.Count - 1 ? PathType.Linear : controlPoints[i].Type); args[i + 1] = (controlPoints[i].Position + slider.Position, i == controlPoints.Count - 1 ? PathType.LINEAR : controlPoints[i].Type);
} }
args[^1] = (circle2.Position, null); args[^1] = (circle2.Position, null);
@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
mergeSelection(); mergeSelection();
AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor( AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor(
(pos: circle1.Position, pathType: PathType.Linear), (pos: circle1.Position, pathType: PathType.LINEAR),
(pos: circle2.Position, pathType: null))); (pos: circle2.Position, pathType: null)));
AddAssert("samples exist", sliderSampleExist); AddAssert("samples exist", sliderSampleExist);
@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
mergeSelection(); mergeSelection();
AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor( AddAssert("slider created", () => circle1 is not null && circle2 is not null && sliderCreatedFor(
(pos: circle1.Position, pathType: PathType.Linear), (pos: circle1.Position, pathType: PathType.LINEAR),
(pos: circle2.Position, pathType: null))); (pos: circle2.Position, pathType: null)));
} }

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
PathControlPoint[] points = PathControlPoint[] points =
{ {
new PathControlPoint(new Vector2(0), PathType.PerfectCurve), new PathControlPoint(new Vector2(0), PathType.PERFECTCURVE),
new PathControlPoint(new Vector2(-100, 0)), new PathControlPoint(new Vector2(-100, 0)),
new PathControlPoint(new Vector2(100, 20)) new PathControlPoint(new Vector2(100, 20))
}; };

View File

@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
createVisualiser(true); createVisualiser(true);
addControlPointStep(new Vector2(200), PathType.Bezier); addControlPointStep(new Vector2(200), PathType.BEZIER);
addControlPointStep(new Vector2(300)); addControlPointStep(new Vector2(300));
addControlPointStep(new Vector2(500, 300)); addControlPointStep(new Vector2(500, 300));
addControlPointStep(new Vector2(700, 200)); addControlPointStep(new Vector2(700, 200));
@ -63,9 +63,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("select control point", () => visualiser.Pieces[1].IsSelected.Value = true); AddStep("select control point", () => visualiser.Pieces[1].IsSelected.Value = true);
addContextMenuItemStep("Perfect curve"); addContextMenuItemStep("Perfect curve");
assertControlPointPathType(0, PathType.Bezier); assertControlPointPathType(0, PathType.BEZIER);
assertControlPointPathType(1, PathType.PerfectCurve); assertControlPointPathType(1, PathType.PERFECTCURVE);
assertControlPointPathType(3, PathType.Bezier); assertControlPointPathType(3, PathType.BEZIER);
} }
[Test] [Test]
@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
createVisualiser(true); createVisualiser(true);
addControlPointStep(new Vector2(200), PathType.Bezier); addControlPointStep(new Vector2(200), PathType.BEZIER);
addControlPointStep(new Vector2(300)); addControlPointStep(new Vector2(300));
addControlPointStep(new Vector2(500, 300)); addControlPointStep(new Vector2(500, 300));
addControlPointStep(new Vector2(700, 200)); addControlPointStep(new Vector2(700, 200));
@ -83,8 +83,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("select control point", () => visualiser.Pieces[2].IsSelected.Value = true); AddStep("select control point", () => visualiser.Pieces[2].IsSelected.Value = true);
addContextMenuItemStep("Perfect curve"); addContextMenuItemStep("Perfect curve");
assertControlPointPathType(0, PathType.Bezier); assertControlPointPathType(0, PathType.BEZIER);
assertControlPointPathType(2, PathType.PerfectCurve); assertControlPointPathType(2, PathType.PERFECTCURVE);
assertControlPointPathType(4, null); assertControlPointPathType(4, null);
} }
@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
createVisualiser(true); createVisualiser(true);
addControlPointStep(new Vector2(200), PathType.Bezier); addControlPointStep(new Vector2(200), PathType.BEZIER);
addControlPointStep(new Vector2(300)); addControlPointStep(new Vector2(300));
addControlPointStep(new Vector2(500, 300)); addControlPointStep(new Vector2(500, 300));
addControlPointStep(new Vector2(700, 200)); addControlPointStep(new Vector2(700, 200));
@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("select control point", () => visualiser.Pieces[3].IsSelected.Value = true); AddStep("select control point", () => visualiser.Pieces[3].IsSelected.Value = true);
addContextMenuItemStep("Perfect curve"); addContextMenuItemStep("Perfect curve");
assertControlPointPathType(0, PathType.Bezier); assertControlPointPathType(0, PathType.BEZIER);
AddAssert("point 3 is not inherited", () => slider.Path.ControlPoints[3].Type != null); AddAssert("point 3 is not inherited", () => slider.Path.ControlPoints[3].Type != null);
} }
@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
createVisualiser(true); createVisualiser(true);
addControlPointStep(new Vector2(200), PathType.Linear); addControlPointStep(new Vector2(200), PathType.LINEAR);
addControlPointStep(new Vector2(300)); addControlPointStep(new Vector2(300));
addControlPointStep(new Vector2(500, 300)); addControlPointStep(new Vector2(500, 300));
addControlPointStep(new Vector2(700, 200)); addControlPointStep(new Vector2(700, 200));
@ -123,9 +123,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("select control point", () => visualiser.Pieces[1].IsSelected.Value = true); AddStep("select control point", () => visualiser.Pieces[1].IsSelected.Value = true);
addContextMenuItemStep("Perfect curve"); addContextMenuItemStep("Perfect curve");
assertControlPointPathType(0, PathType.Linear); assertControlPointPathType(0, PathType.LINEAR);
assertControlPointPathType(1, PathType.PerfectCurve); assertControlPointPathType(1, PathType.PERFECTCURVE);
assertControlPointPathType(3, PathType.Linear); assertControlPointPathType(3, PathType.LINEAR);
} }
[Test] [Test]
@ -133,18 +133,18 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
createVisualiser(true); createVisualiser(true);
addControlPointStep(new Vector2(200), PathType.Bezier); addControlPointStep(new Vector2(200), PathType.BEZIER);
addControlPointStep(new Vector2(300), PathType.PerfectCurve); addControlPointStep(new Vector2(300), PathType.PERFECTCURVE);
addControlPointStep(new Vector2(500, 300)); addControlPointStep(new Vector2(500, 300));
addControlPointStep(new Vector2(700, 200), PathType.Bezier); addControlPointStep(new Vector2(700, 200), PathType.BEZIER);
addControlPointStep(new Vector2(500, 100)); addControlPointStep(new Vector2(500, 100));
moveMouseToControlPoint(3); moveMouseToControlPoint(3);
AddStep("select control point", () => visualiser.Pieces[3].IsSelected.Value = true); AddStep("select control point", () => visualiser.Pieces[3].IsSelected.Value = true);
addContextMenuItemStep("Inherit"); addContextMenuItemStep("Inherit");
assertControlPointPathType(0, PathType.Bezier); assertControlPointPathType(0, PathType.BEZIER);
assertControlPointPathType(1, PathType.Bezier); assertControlPointPathType(1, PathType.BEZIER);
assertControlPointPathType(3, null); assertControlPointPathType(3, null);
} }

View File

@ -38,9 +38,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
Position = new Vector2(256, 192), Position = new Vector2(256, 192),
Path = new SliderPath(new[] Path = new SliderPath(new[]
{ {
new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE),
new PathControlPoint(new Vector2(150, 150)), new PathControlPoint(new Vector2(150, 150)),
new PathControlPoint(new Vector2(300, 0), PathType.PerfectCurve), new PathControlPoint(new Vector2(300, 0), PathType.PERFECTCURVE),
new PathControlPoint(new Vector2(400, 0)), new PathControlPoint(new Vector2(400, 0)),
new PathControlPoint(new Vector2(400, 150)) new PathControlPoint(new Vector2(400, 150))
}) })
@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
assertControlPointPosition(1, new Vector2(150, 50)); assertControlPointPosition(1, new Vector2(150, 50));
assertControlPointType(0, PathType.PerfectCurve); assertControlPointType(0, PathType.PERFECTCURVE);
} }
[Test] [Test]
@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece<Slider>>().Count(piece => piece.IsSelected.Value) == 3); AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece<Slider>>().Count(piece => piece.IsSelected.Value) == 3);
assertControlPointPosition(2, new Vector2(450, 50)); assertControlPointPosition(2, new Vector2(450, 50));
assertControlPointType(2, PathType.PerfectCurve); assertControlPointType(2, PathType.PERFECTCURVE);
assertControlPointPosition(3, new Vector2(550, 50)); assertControlPointPosition(3, new Vector2(550, 50));
@ -249,7 +249,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddAssert("slider moved", () => Precision.AlmostEquals(slider.Position, new Vector2(256, 192) + new Vector2(150, 50))); AddAssert("slider moved", () => Precision.AlmostEquals(slider.Position, new Vector2(256, 192) + new Vector2(150, 50)));
assertControlPointPosition(0, Vector2.Zero); assertControlPointPosition(0, Vector2.Zero);
assertControlPointType(0, PathType.PerfectCurve); assertControlPointType(0, PathType.PERFECTCURVE);
assertControlPointPosition(1, new Vector2(0, 100)); assertControlPointPosition(1, new Vector2(0, 100));
@ -272,7 +272,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
assertControlPointPosition(1, new Vector2(400, 0.01f)); assertControlPointPosition(1, new Vector2(400, 0.01f));
assertControlPointType(0, PathType.Bezier); assertControlPointType(0, PathType.BEZIER);
} }
[Test] [Test]
@ -282,13 +282,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
addMovementStep(new Vector2(400, 0.01f)); addMovementStep(new Vector2(400, 0.01f));
assertControlPointType(0, PathType.Bezier); assertControlPointType(0, PathType.BEZIER);
addMovementStep(new Vector2(150, 50)); addMovementStep(new Vector2(150, 50));
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
assertControlPointPosition(1, new Vector2(150, 50)); assertControlPointPosition(1, new Vector2(150, 50));
assertControlPointType(0, PathType.PerfectCurve); assertControlPointType(0, PathType.PERFECTCURVE);
} }
[Test] [Test]
@ -298,32 +298,32 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
addMovementStep(new Vector2(350, 0.01f)); addMovementStep(new Vector2(350, 0.01f));
assertControlPointType(2, PathType.Bezier); assertControlPointType(2, PathType.BEZIER);
addMovementStep(new Vector2(150, 150)); addMovementStep(new Vector2(150, 150));
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
assertControlPointPosition(4, new Vector2(150, 150)); assertControlPointPosition(4, new Vector2(150, 150));
assertControlPointType(2, PathType.PerfectCurve); assertControlPointType(2, PathType.PERFECTCURVE);
} }
[Test] [Test]
public void TestDragControlPointPathAfterChangingType() public void TestDragControlPointPathAfterChangingType()
{ {
AddStep("change type to bezier", () => slider.Path.ControlPoints[2].Type = PathType.Bezier); AddStep("change type to bezier", () => slider.Path.ControlPoints[2].Type = PathType.BEZIER);
AddStep("add point", () => slider.Path.ControlPoints.Add(new PathControlPoint(new Vector2(500, 10)))); AddStep("add point", () => slider.Path.ControlPoints.Add(new PathControlPoint(new Vector2(500, 10))));
AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type = PathType.PerfectCurve); AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type = PathType.PERFECTCURVE);
moveMouseToControlPoint(4); moveMouseToControlPoint(4);
AddStep("hold", () => InputManager.PressButton(MouseButton.Left)); AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
assertControlPointType(3, PathType.PerfectCurve); assertControlPointType(3, PathType.PERFECTCURVE);
addMovementStep(new Vector2(350, 0.01f)); addMovementStep(new Vector2(350, 0.01f));
AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left));
assertControlPointPosition(4, new Vector2(350, 0.01f)); assertControlPointPosition(4, new Vector2(350, 0.01f));
assertControlPointType(3, PathType.Bezier); assertControlPointType(3, PathType.BEZIER);
} }
private void addMovementStep(Vector2 relativePosition) private void addMovementStep(Vector2 relativePosition)

View File

@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
PathControlPoint[] points = PathControlPoint[] points =
{ {
new PathControlPoint(new Vector2(0), PathType.Linear), new PathControlPoint(new Vector2(0), PathType.LINEAR),
new PathControlPoint(new Vector2(100, 0)), new PathControlPoint(new Vector2(100, 0)),
}; };
@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
PathControlPoint[] points = PathControlPoint[] points =
{ {
new PathControlPoint(new Vector2(0), PathType.Linear), new PathControlPoint(new Vector2(0), PathType.LINEAR),
new PathControlPoint(new Vector2(100, 0)), new PathControlPoint(new Vector2(100, 0)),
}; };
@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
PathControlPoint[] points = PathControlPoint[] points =
{ {
new PathControlPoint(new Vector2(0), PathType.PerfectCurve), new PathControlPoint(new Vector2(0), PathType.PERFECTCURVE),
new PathControlPoint(new Vector2(100, 0)), new PathControlPoint(new Vector2(100, 0)),
new PathControlPoint(new Vector2(0, 10)) new PathControlPoint(new Vector2(0, 10))
}; };
@ -165,7 +165,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
PathControlPoint[] points = PathControlPoint[] points =
{ {
new PathControlPoint(new Vector2(0), PathType.Linear), new PathControlPoint(new Vector2(0), PathType.LINEAR),
new PathControlPoint(new Vector2(0, 50)), new PathControlPoint(new Vector2(0, 50)),
new PathControlPoint(new Vector2(0, 100)) new PathControlPoint(new Vector2(0, 100))
}; };

View File

@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true); assertPlaced(true);
assertLength(200); assertLength(200);
assertControlPointCount(2); assertControlPointCount(2);
assertControlPointType(0, PathType.Linear); assertControlPointType(0, PathType.LINEAR);
} }
[Test] [Test]
@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true); assertPlaced(true);
assertControlPointCount(2); assertControlPointCount(2);
assertControlPointType(0, PathType.Linear); assertControlPointType(0, PathType.LINEAR);
} }
[Test] [Test]
@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true); assertPlaced(true);
assertControlPointCount(3); assertControlPointCount(3);
assertControlPointPosition(1, new Vector2(100, 0)); assertControlPointPosition(1, new Vector2(100, 0));
assertControlPointType(0, PathType.PerfectCurve); assertControlPointType(0, PathType.PERFECTCURVE);
} }
[Test] [Test]
@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertControlPointCount(4); assertControlPointCount(4);
assertControlPointPosition(1, new Vector2(100, 0)); assertControlPointPosition(1, new Vector2(100, 0));
assertControlPointPosition(2, new Vector2(100, 100)); assertControlPointPosition(2, new Vector2(100, 100));
assertControlPointType(0, PathType.Bezier); assertControlPointType(0, PathType.BEZIER);
} }
[Test] [Test]
@ -131,8 +131,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true); assertPlaced(true);
assertControlPointCount(3); assertControlPointCount(3);
assertControlPointPosition(1, new Vector2(100, 0)); assertControlPointPosition(1, new Vector2(100, 0));
assertControlPointType(0, PathType.Linear); assertControlPointType(0, PathType.LINEAR);
assertControlPointType(1, PathType.Linear); assertControlPointType(1, PathType.LINEAR);
} }
[Test] [Test]
@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true); assertPlaced(true);
assertControlPointCount(2); assertControlPointCount(2);
assertControlPointType(0, PathType.Linear); assertControlPointType(0, PathType.LINEAR);
assertLength(100); assertLength(100);
} }
@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true); assertPlaced(true);
assertControlPointCount(3); assertControlPointCount(3);
assertControlPointType(0, PathType.PerfectCurve); assertControlPointType(0, PathType.PERFECTCURVE);
} }
[Test] [Test]
@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true); assertPlaced(true);
assertControlPointCount(4); assertControlPointCount(4);
assertControlPointType(0, PathType.Bezier); assertControlPointType(0, PathType.BEZIER);
} }
[Test] [Test]
@ -216,8 +216,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertControlPointCount(3); assertControlPointCount(3);
assertControlPointPosition(1, new Vector2(100, 0)); assertControlPointPosition(1, new Vector2(100, 0));
assertControlPointPosition(2, new Vector2(100)); assertControlPointPosition(2, new Vector2(100));
assertControlPointType(0, PathType.Linear); assertControlPointType(0, PathType.LINEAR);
assertControlPointType(1, PathType.Linear); assertControlPointType(1, PathType.LINEAR);
} }
[Test] [Test]
@ -240,8 +240,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertControlPointCount(4); assertControlPointCount(4);
assertControlPointPosition(1, new Vector2(100, 0)); assertControlPointPosition(1, new Vector2(100, 0));
assertControlPointPosition(2, new Vector2(100)); assertControlPointPosition(2, new Vector2(100));
assertControlPointType(0, PathType.Linear); assertControlPointType(0, PathType.LINEAR);
assertControlPointType(1, PathType.PerfectCurve); assertControlPointType(1, PathType.PERFECTCURVE);
} }
[Test] [Test]
@ -269,8 +269,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertControlPointPosition(2, new Vector2(100)); assertControlPointPosition(2, new Vector2(100));
assertControlPointPosition(3, new Vector2(200, 100)); assertControlPointPosition(3, new Vector2(200, 100));
assertControlPointPosition(4, new Vector2(200)); assertControlPointPosition(4, new Vector2(200));
assertControlPointType(0, PathType.PerfectCurve); assertControlPointType(0, PathType.PERFECTCURVE);
assertControlPointType(2, PathType.PerfectCurve); assertControlPointType(2, PathType.PERFECTCURVE);
} }
[Test] [Test]
@ -287,7 +287,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true); assertPlaced(true);
assertLength(200); assertLength(200);
assertControlPointCount(2); assertControlPointCount(2);
assertControlPointType(0, PathType.Linear); assertControlPointType(0, PathType.LINEAR);
} }
[Test] [Test]
@ -306,7 +306,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true); assertPlaced(true);
assertControlPointCount(3); assertControlPointCount(3);
assertControlPointType(0, PathType.Bezier); assertControlPointType(0, PathType.BEZIER);
} }
[Test] [Test]
@ -326,7 +326,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true); assertPlaced(true);
assertControlPointCount(3); assertControlPointCount(3);
assertControlPointType(0, PathType.PerfectCurve); assertControlPointType(0, PathType.PERFECTCURVE);
} }
[Test] [Test]
@ -347,7 +347,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true); assertPlaced(true);
assertControlPointCount(3); assertControlPointCount(3);
assertControlPointType(0, PathType.PerfectCurve); assertControlPointType(0, PathType.PERFECTCURVE);
} }
[Test] [Test]
@ -368,7 +368,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true); assertPlaced(true);
assertControlPointCount(3); assertControlPointCount(3);
assertControlPointType(0, PathType.Bezier); assertControlPointType(0, PathType.BEZIER);
} }
[Test] [Test]
@ -385,7 +385,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
assertPlaced(true); assertPlaced(true);
assertControlPointCount(3); assertControlPointCount(3);
assertControlPointType(0, PathType.PerfectCurve); assertControlPointType(0, PathType.PERFECTCURVE);
} }
private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position))); private void addMovementStep(Vector2 position) => AddStep($"move mouse to {position}", () => InputManager.MoveMouseTo(InputManager.ToScreenSpace(position)));

View File

@ -22,12 +22,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
private readonly PathControlPoint[][] paths = private readonly PathControlPoint[][] paths =
{ {
createPathSegment( createPathSegment(
PathType.PerfectCurve, PathType.PERFECTCURVE,
new Vector2(200, -50), new Vector2(200, -50),
new Vector2(250, 0) new Vector2(250, 0)
), ),
createPathSegment( createPathSegment(
PathType.Linear, PathType.LINEAR,
new Vector2(100, 0), new Vector2(100, 0),
new Vector2(100, 100) new Vector2(100, 100)
) )

View File

@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
slider = new Slider slider = new Slider
{ {
Position = new Vector2(256, 192), Position = new Vector2(256, 192),
Path = new SliderPath(PathType.Bezier, new[] Path = new SliderPath(PathType.BEZIER, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(150, 150), new Vector2(150, 150),

View File

@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
ControlPoints = ControlPoints =
{ {
new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE),
new PathControlPoint(new Vector2(136, 205)), new PathControlPoint(new Vector2(136, 205)),
new PathControlPoint(new Vector2(-4, 226)) new PathControlPoint(new Vector2(-4, 226))
} }
@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
OsuSelectionHandler selectionHandler; OsuSelectionHandler selectionHandler;
AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PerfectCurve); AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE);
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("rotate 90 degrees ccw", () => AddStep("rotate 90 degrees ccw", () =>
@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
selectionHandler.HandleRotation(-90); selectionHandler.HandleRotation(-90);
}); });
AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PerfectCurve); AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE);
} }
[Test] [Test]
@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{ {
OsuSelectionHandler selectionHandler; OsuSelectionHandler selectionHandler;
AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PerfectCurve); AddAssert("first control point perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE);
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
AddStep("flip slider horizontally", () => AddStep("flip slider horizontally", () =>
@ -232,7 +232,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
selectionHandler.OnPressed(new KeyBindingPressEvent<GlobalAction>(InputManager.CurrentState, GlobalAction.EditorFlipVertically)); selectionHandler.OnPressed(new KeyBindingPressEvent<GlobalAction>(InputManager.CurrentState, GlobalAction.EditorFlipVertically));
}); });
AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PerfectCurve); AddAssert("first control point still perfect", () => slider.Path.ControlPoints[0].Type == PathType.PERFECTCURVE);
} }
[Test] [Test]

View File

@ -45,9 +45,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
Position = new Vector2(0, 50), Position = new Vector2(0, 50),
Path = new SliderPath(new[] Path = new SliderPath(new[]
{ {
new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE),
new PathControlPoint(new Vector2(150, 150)), new PathControlPoint(new Vector2(150, 150)),
new PathControlPoint(new Vector2(300, 0), PathType.PerfectCurve), new PathControlPoint(new Vector2(300, 0), PathType.PERFECTCURVE),
new PathControlPoint(new Vector2(400, 0)), new PathControlPoint(new Vector2(400, 0)),
new PathControlPoint(new Vector2(400, 150)) new PathControlPoint(new Vector2(400, 150))
}) })
@ -73,20 +73,20 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddAssert("slider split", () => slider is not null && EditorBeatmap.HitObjects.Count == 2 && AddAssert("slider split", () => slider is not null && EditorBeatmap.HitObjects.Count == 2 &&
sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, EditorBeatmap.HitObjects[1].StartTime - split_gap, sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, EditorBeatmap.HitObjects[1].StartTime - split_gap,
(new Vector2(0, 50), PathType.PerfectCurve), (new Vector2(0, 50), PathType.PERFECTCURVE),
(new Vector2(150, 200), null), (new Vector2(150, 200), null),
(new Vector2(300, 50), null) (new Vector2(300, 50), null)
) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[1], slider.StartTime, endTime + split_gap, ) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[1], slider.StartTime, endTime + split_gap,
(new Vector2(300, 50), PathType.PerfectCurve), (new Vector2(300, 50), PathType.PERFECTCURVE),
(new Vector2(400, 50), null), (new Vector2(400, 50), null),
(new Vector2(400, 200), null) (new Vector2(400, 200), null)
)); ));
AddStep("undo", () => Editor.Undo()); AddStep("undo", () => Editor.Undo());
AddAssert("original slider restored", () => EditorBeatmap.HitObjects.Count == 1 && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, endTime, AddAssert("original slider restored", () => EditorBeatmap.HitObjects.Count == 1 && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, endTime,
(new Vector2(0, 50), PathType.PerfectCurve), (new Vector2(0, 50), PathType.PERFECTCURVE),
(new Vector2(150, 200), null), (new Vector2(150, 200), null),
(new Vector2(300, 50), PathType.PerfectCurve), (new Vector2(300, 50), PathType.PERFECTCURVE),
(new Vector2(400, 50), null), (new Vector2(400, 50), null),
(new Vector2(400, 200), null) (new Vector2(400, 200), null)
)); ));
@ -104,11 +104,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
Position = new Vector2(0, 50), Position = new Vector2(0, 50),
Path = new SliderPath(new[] Path = new SliderPath(new[]
{ {
new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE),
new PathControlPoint(new Vector2(150, 150)), new PathControlPoint(new Vector2(150, 150)),
new PathControlPoint(new Vector2(300, 0), PathType.Bezier), new PathControlPoint(new Vector2(300, 0), PathType.BEZIER),
new PathControlPoint(new Vector2(400, 0)), new PathControlPoint(new Vector2(400, 0)),
new PathControlPoint(new Vector2(400, 150), PathType.Catmull), new PathControlPoint(new Vector2(400, 150), PathType.CATMULL),
new PathControlPoint(new Vector2(300, 200)), new PathControlPoint(new Vector2(300, 200)),
new PathControlPoint(new Vector2(400, 250)) new PathControlPoint(new Vector2(400, 250))
}) })
@ -139,15 +139,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddAssert("slider split", () => slider is not null && EditorBeatmap.HitObjects.Count == 3 && AddAssert("slider split", () => slider is not null && EditorBeatmap.HitObjects.Count == 3 &&
sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, EditorBeatmap.HitObjects[1].StartTime - split_gap, sliderCreatedFor((Slider)EditorBeatmap.HitObjects[0], 0, EditorBeatmap.HitObjects[1].StartTime - split_gap,
(new Vector2(0, 50), PathType.PerfectCurve), (new Vector2(0, 50), PathType.PERFECTCURVE),
(new Vector2(150, 200), null), (new Vector2(150, 200), null),
(new Vector2(300, 50), null) (new Vector2(300, 50), null)
) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[1], EditorBeatmap.HitObjects[0].GetEndTime() + split_gap, slider.StartTime - split_gap, ) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[1], EditorBeatmap.HitObjects[0].GetEndTime() + split_gap, slider.StartTime - split_gap,
(new Vector2(300, 50), PathType.Bezier), (new Vector2(300, 50), PathType.BEZIER),
(new Vector2(400, 50), null), (new Vector2(400, 50), null),
(new Vector2(400, 200), null) (new Vector2(400, 200), null)
) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[2], EditorBeatmap.HitObjects[1].GetEndTime() + split_gap, endTime + split_gap * 2, ) && sliderCreatedFor((Slider)EditorBeatmap.HitObjects[2], EditorBeatmap.HitObjects[1].GetEndTime() + split_gap, endTime + split_gap * 2,
(new Vector2(400, 200), PathType.Catmull), (new Vector2(400, 200), PathType.CATMULL),
(new Vector2(300, 250), null), (new Vector2(300, 250), null),
(new Vector2(400, 300), null) (new Vector2(400, 300), null)
)); ));
@ -165,9 +165,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
Position = new Vector2(0, 50), Position = new Vector2(0, 50),
Path = new SliderPath(new[] Path = new SliderPath(new[]
{ {
new PathControlPoint(Vector2.Zero, PathType.PerfectCurve), new PathControlPoint(Vector2.Zero, PathType.PERFECTCURVE),
new PathControlPoint(new Vector2(150, 150)), new PathControlPoint(new Vector2(150, 150)),
new PathControlPoint(new Vector2(300, 0), PathType.PerfectCurve), new PathControlPoint(new Vector2(300, 0), PathType.PERFECTCURVE),
new PathControlPoint(new Vector2(400, 0)), new PathControlPoint(new Vector2(400, 0)),
new PathControlPoint(new Vector2(400, 150)) new PathControlPoint(new Vector2(400, 150))
}) })

View File

@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
PathControlPoint[] points = PathControlPoint[] points =
{ {
new PathControlPoint(new Vector2(0), PathType.Linear), new PathControlPoint(new Vector2(0), PathType.LINEAR),
new PathControlPoint(new Vector2(100, 0)), new PathControlPoint(new Vector2(100, 0)),
}; };

View File

@ -81,12 +81,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
new Slider new Slider
{ {
StartTime = 3200, StartTime = 3200,
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), })
}, },
new Slider new Slider
{ {
StartTime = 5200, StartTime = 5200,
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), })
} }
} }
}, },
@ -105,12 +105,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
new Slider new Slider
{ {
StartTime = 1000, StartTime = 1000,
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), })
}, },
new Slider new Slider
{ {
StartTime = 4000, StartTime = 4000,
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), })
}, },
} }
}, },
@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
{ {
StartTime = 3000, StartTime = 3000,
Position = new Vector2(156, 242), Position = new Vector2(156, 242),
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(200, 0), }) Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(200, 0), })
}, },
new Spinner new Spinner
{ {

View File

@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
var slider = new Slider var slider = new Slider
{ {
StartTime = 1000, StartTime = 1000,
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(100, 0), }) Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), })
}; };
CreateHitObjectTest(new HitObjectTestData(slider), shouldMiss); CreateHitObjectTest(new HitObjectTestData(slider), shouldMiss);

View File

@ -26,9 +26,9 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
ControlPoints = ControlPoints =
{ {
new PathControlPoint(new Vector2(), PathType.Linear), new PathControlPoint(new Vector2(), PathType.LINEAR),
new PathControlPoint(new Vector2(-64, -128), PathType.Linear), // absolute position: (64, 0) new PathControlPoint(new Vector2(-64, -128), PathType.LINEAR), // absolute position: (64, 0)
new PathControlPoint(new Vector2(-128, 0), PathType.Linear) // absolute position: (0, 128) new PathControlPoint(new Vector2(-128, 0), PathType.LINEAR) // absolute position: (0, 128)
} }
}, },
RepeatCount = 1 RepeatCount = 1

View File

@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = Time.Current + 500, StartTime = Time.Current + 500,
Position = new Vector2(250), Position = new Vector2(250),
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(0, 100), new Vector2(0, 100),

View File

@ -264,7 +264,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = time_slider, StartTime = time_slider,
Position = positionSlider, Position = positionSlider,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(50, 0), new Vector2(50, 0),
@ -308,7 +308,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = time_slider, StartTime = time_slider,
Position = positionSlider, Position = positionSlider,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(50, 0), new Vector2(50, 0),
@ -391,7 +391,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = time_slider, StartTime = time_slider,
Position = positionSlider, Position = positionSlider,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(25, 0), new Vector2(25, 0),
@ -428,7 +428,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = time_first_slider, StartTime = time_first_slider,
Position = positionFirstSlider, Position = positionFirstSlider,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(25, 0), new Vector2(25, 0),
@ -438,7 +438,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = time_second_slider, StartTime = time_second_slider,
Position = positionSecondSlider, Position = positionSecondSlider,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(25, 0), new Vector2(25, 0),
@ -521,7 +521,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = time_first_slider, StartTime = time_first_slider,
Position = positionFirstSlider, Position = positionFirstSlider,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(25, 0), new Vector2(25, 0),
@ -531,7 +531,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = time_second_slider, StartTime = time_second_slider,
Position = positionSecondSlider, Position = positionSecondSlider,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(25, 0), new Vector2(25, 0),
@ -571,7 +571,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = time_first_slider, StartTime = time_first_slider,
Position = positionFirstSlider, Position = positionFirstSlider,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(25, 0), new Vector2(25, 0),
@ -581,7 +581,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = time_second_slider, StartTime = time_second_slider,
Position = positionSecondSlider, Position = positionSecondSlider,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(25, 0), new Vector2(25, 0),

View File

@ -219,7 +219,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = Time.Current + time_offset, StartTime = Time.Current + time_offset,
Position = new Vector2(239, 176), Position = new Vector2(239, 176),
Path = new SliderPath(PathType.PerfectCurve, new[] Path = new SliderPath(PathType.PERFECTCURVE, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(154, 28), new Vector2(154, 28),
@ -255,7 +255,7 @@ namespace osu.Game.Rulesets.Osu.Tests
SliderVelocityMultiplier = speedMultiplier, SliderVelocityMultiplier = speedMultiplier,
StartTime = Time.Current + time_offset, StartTime = Time.Current + time_offset,
Position = new Vector2(0, -(distance / 2)), Position = new Vector2(0, -(distance / 2)),
Path = new SliderPath(PathType.PerfectCurve, new[] Path = new SliderPath(PathType.PERFECTCURVE, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(0, distance), new Vector2(0, distance),
@ -273,7 +273,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = Time.Current + time_offset, StartTime = Time.Current + time_offset,
Position = new Vector2(-max_length / 2, 0), Position = new Vector2(-max_length / 2, 0),
Path = new SliderPath(PathType.PerfectCurve, new[] Path = new SliderPath(PathType.PERFECTCURVE, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(max_length / 2, max_length / 2), new Vector2(max_length / 2, max_length / 2),
@ -293,7 +293,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = Time.Current + time_offset, StartTime = Time.Current + time_offset,
Position = new Vector2(-max_length / 2, 0), Position = new Vector2(-max_length / 2, 0),
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(max_length * 0.375f, max_length * 0.18f), new Vector2(max_length * 0.375f, max_length * 0.18f),
@ -316,7 +316,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = Time.Current + time_offset, StartTime = Time.Current + time_offset,
Position = new Vector2(-max_length / 2, 0), Position = new Vector2(-max_length / 2, 0),
Path = new SliderPath(PathType.Bezier, new[] Path = new SliderPath(PathType.BEZIER, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(max_length * 0.375f, max_length * 0.18f), new Vector2(max_length * 0.375f, max_length * 0.18f),
@ -338,7 +338,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = Time.Current + time_offset, StartTime = Time.Current + time_offset,
Position = new Vector2(0, 0), Position = new Vector2(0, 0),
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(-max_length / 2, 0), new Vector2(-max_length / 2, 0),
@ -365,7 +365,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = Time.Current + time_offset, StartTime = Time.Current + time_offset,
Position = new Vector2(-max_length / 4, 0), Position = new Vector2(-max_length / 4, 0),
Path = new SliderPath(PathType.Catmull, new[] Path = new SliderPath(PathType.CATMULL, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(max_length * 0.125f, max_length * 0.125f), new Vector2(max_length * 0.125f, max_length * 0.125f),

View File

@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Position = new Vector2(256, 192), Position = new Vector2(256, 192),
IndexInCurrentCombo = 0, IndexInCurrentCombo = 0,
StartTime = Time.Current, StartTime = Time.Current,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(150, 100), new Vector2(150, 100),
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Position = new Vector2(256, 192), Position = new Vector2(256, 192),
ComboIndex = 1, ComboIndex = 1,
StartTime = dho.HitObject.StartTime, StartTime = dho.HitObject.StartTime,
Path = new SliderPath(PathType.Bezier, new[] Path = new SliderPath(PathType.BEZIER, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(150, 100), new Vector2(150, 100),
@ -80,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Position = new Vector2(256, 192), Position = new Vector2(256, 192),
IndexInCurrentCombo = 0, IndexInCurrentCombo = 0,
StartTime = Time.Current, StartTime = Time.Current,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(150, 100), new Vector2(150, 100),

View File

@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Tests
StartTime = time_slider_start, StartTime = time_slider_start,
Position = new Vector2(0, 0), Position = new Vector2(0, 0),
SliderVelocityMultiplier = velocity, SliderVelocityMultiplier = velocity,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(followCircleRadius, 0), new Vector2(followCircleRadius, 0),

View File

@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Position = new Vector2(0, 0), Position = new Vector2(0, 0),
SliderVelocityMultiplier = 10f, SliderVelocityMultiplier = 10f,
RepeatCount = repeatCount, RepeatCount = repeatCount,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(sliderLength, 0), new Vector2(sliderLength, 0),
@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Position = new Vector2(0, 0), Position = new Vector2(0, 0),
SliderVelocityMultiplier = 10f, SliderVelocityMultiplier = 10f,
RepeatCount = repeatCount, RepeatCount = repeatCount,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(sliderLength, 0), new Vector2(sliderLength, 0),
@ -145,7 +145,7 @@ namespace osu.Game.Rulesets.Osu.Tests
StartTime = time_slider_start, StartTime = time_slider_start,
Position = new Vector2(0, 0), Position = new Vector2(0, 0),
SliderVelocityMultiplier = 10f, SliderVelocityMultiplier = 10f,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(slider_path_length * 10, 0), new Vector2(slider_path_length * 10, 0),
@ -478,7 +478,7 @@ namespace osu.Game.Rulesets.Osu.Tests
StartTime = time_slider_start, StartTime = time_slider_start,
Position = new Vector2(0, 0), Position = new Vector2(0, 0),
SliderVelocityMultiplier = 0.1f, SliderVelocityMultiplier = 0.1f,
Path = new SliderPath(PathType.PerfectCurve, new[] Path = new SliderPath(PathType.PERFECTCURVE, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(slider_path_length, 0), new Vector2(slider_path_length, 0),

View File

@ -217,7 +217,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = 3000, StartTime = 3000,
Position = new Vector2(100, 100), Position = new Vector2(100, 100),
Path = new SliderPath(PathType.PerfectCurve, new[] Path = new SliderPath(PathType.PERFECTCURVE, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(300, 200) new Vector2(300, 200)
@ -227,7 +227,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = 13000, StartTime = 13000,
Position = new Vector2(100, 100), Position = new Vector2(100, 100),
Path = new SliderPath(PathType.PerfectCurve, new[] Path = new SliderPath(PathType.PERFECTCURVE, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(300, 200) new Vector2(300, 200)
@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = 23000, StartTime = 23000,
Position = new Vector2(100, 100), Position = new Vector2(100, 100),
Path = new SliderPath(PathType.PerfectCurve, new[] Path = new SliderPath(PathType.PERFECTCURVE, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(300, 200) new Vector2(300, 200)

View File

@ -196,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = time_slider, StartTime = time_slider,
Position = positionSlider, Position = positionSlider,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(25, 0), new Vector2(25, 0),
@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = time_slider, StartTime = time_slider,
Position = positionSlider, Position = positionSlider,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(25, 0), new Vector2(25, 0),
@ -318,7 +318,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = time_slider, StartTime = time_slider,
Position = positionSlider, Position = positionSlider,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(25, 0), new Vector2(25, 0),
@ -352,7 +352,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = time_first_slider, StartTime = time_first_slider,
Position = positionFirstSlider, Position = positionFirstSlider,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(25, 0), new Vector2(25, 0),
@ -362,7 +362,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{ {
StartTime = time_second_slider, StartTime = time_second_slider,
Position = positionSecondSlider, Position = positionSecondSlider,
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(25, 0), new Vector2(25, 0),

View File

@ -221,11 +221,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
/// </summary> /// </summary>
private void updatePathType() private void updatePathType()
{ {
if (ControlPoint.Type != PathType.PerfectCurve) if (ControlPoint.Type != PathType.PERFECTCURVE)
return; return;
if (PointsInSegment.Count > 3) if (PointsInSegment.Count > 3)
ControlPoint.Type = PathType.Bezier; ControlPoint.Type = PathType.BEZIER;
if (PointsInSegment.Count != 3) if (PointsInSegment.Count != 3)
return; return;
@ -233,7 +233,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
ReadOnlySpan<Vector2> points = PointsInSegment.Select(p => p.Position).ToArray(); ReadOnlySpan<Vector2> points = PointsInSegment.Select(p => p.Position).ToArray();
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points); RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points);
if (boundingBox.Width >= 640 || boundingBox.Height >= 480) if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
ControlPoint.Type = PathType.Bezier; ControlPoint.Type = PathType.BEZIER;
} }
/// <summary> /// <summary>
@ -256,18 +256,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private Color4 getColourFromNodeType() private Color4 getColourFromNodeType()
{ {
if (!(ControlPoint.Type is PathType pathType)) if (ControlPoint.Type is not PathType pathType)
return colours.Yellow; return colours.Yellow;
switch (pathType) switch (pathType)
{ {
case PathType.Catmull: case { SplineType: SplineType.Catmull }:
return colours.SeaFoam; return colours.SeaFoam;
case PathType.Bezier: case { SplineType: SplineType.BSpline, Degree: null }:
return colours.Pink; return colours.PinkLighter;
case PathType.PerfectCurve: case { SplineType: SplineType.BSpline, Degree: >= 1 }:
int idx = Math.Clamp(pathType.Degree.Value, 0, 3);
return new[] { colours.PinkDarker, colours.PinkDark, colours.Pink, colours.PinkLight }[idx];
case { SplineType: SplineType.PerfectCurve }:
return colours.PurpleDark; return colours.PurpleDark;
default: default:

View File

@ -242,9 +242,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{ {
int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint); int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint);
switch (type) if (type.HasValue && type.Value.SplineType == SplineType.PerfectCurve)
{ {
case PathType.PerfectCurve:
// Can't always create a circular arc out of 4 or more points, // Can't always create a circular arc out of 4 or more points,
// so we split the segment into one 3-point circular arc segment // so we split the segment into one 3-point circular arc segment
// and one segment of the previous type. // and one segment of the previous type.
@ -252,8 +251,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
if (piece.PointsInSegment.Count > thirdPointIndex + 1) if (piece.PointsInSegment.Count > thirdPointIndex + 1)
piece.PointsInSegment[thirdPointIndex].Type = piece.PointsInSegment[0].Type; piece.PointsInSegment[thirdPointIndex].Type = piece.PointsInSegment[0].Type;
break;
} }
hitObject.Path.ExpectedDistance.Value = null; hitObject.Path.ExpectedDistance.Value = null;
@ -370,10 +367,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
curveTypeItems.Add(createMenuItemForPathType(null)); curveTypeItems.Add(createMenuItemForPathType(null));
// todo: hide/disable items which aren't valid for selected points // todo: hide/disable items which aren't valid for selected points
curveTypeItems.Add(createMenuItemForPathType(PathType.Linear)); curveTypeItems.Add(createMenuItemForPathType(PathType.LINEAR));
curveTypeItems.Add(createMenuItemForPathType(PathType.PerfectCurve)); curveTypeItems.Add(createMenuItemForPathType(PathType.PERFECTCURVE));
curveTypeItems.Add(createMenuItemForPathType(PathType.Bezier)); curveTypeItems.Add(createMenuItemForPathType(PathType.BEZIER));
curveTypeItems.Add(createMenuItemForPathType(PathType.Catmull)); curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(3)));
curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL));
var menuItems = new List<MenuItem> var menuItems = new List<MenuItem>
{ {

View File

@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
HitObject.Path.ControlPoints.Add(segmentStart = new PathControlPoint(Vector2.Zero, PathType.Linear)); HitObject.Path.ControlPoints.Add(segmentStart = new PathControlPoint(Vector2.Zero, PathType.LINEAR));
currentSegmentLength = 1; currentSegmentLength = 1;
} }
@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
Debug.Assert(lastPoint != null); Debug.Assert(lastPoint != null);
segmentStart = lastPoint; segmentStart = lastPoint;
segmentStart.Type = PathType.Linear; segmentStart.Type = PathType.LINEAR;
currentSegmentLength = 1; currentSegmentLength = 1;
} }
@ -173,15 +173,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{ {
case 1: case 1:
case 2: case 2:
segmentStart.Type = PathType.Linear; segmentStart.Type = PathType.LINEAR;
break; break;
case 3: case 3:
segmentStart.Type = PathType.PerfectCurve; segmentStart.Type = PathType.PERFECTCURVE;
break; break;
default: default:
segmentStart.Type = PathType.Bezier; segmentStart.Type = PathType.BEZIER;
break; break;
} }
} }
@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{ {
HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint { Position = Vector2.Zero }); HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint { Position = Vector2.Zero });
// The path type should be adjusted in the progression of updatePathType() (Linear -> PC -> Bezier). // The path type should be adjusted in the progression of updatePathType() (LINEAR -> PC -> BEZIER).
currentSegmentLength++; currentSegmentLength++;
updatePathType(); updatePathType();
} }
@ -210,7 +210,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
HitObject.Path.ControlPoints.Remove(cursor); HitObject.Path.ControlPoints.Remove(cursor);
cursor = null; cursor = null;
// The path type should be adjusted in the reverse progression of updatePathType() (Bezier -> PC -> Linear). // The path type should be adjusted in the reverse progression of updatePathType() (BEZIER -> PC -> LINEAR).
currentSegmentLength--; currentSegmentLength--;
updatePathType(); updatePathType();
} }

View File

@ -320,7 +320,7 @@ namespace osu.Game.Rulesets.Osu.Edit
if (mergedHitObject.Path.ControlPoints.Count == 0) if (mergedHitObject.Path.ControlPoints.Count == 0)
{ {
mergedHitObject.Path.ControlPoints.Add(new PathControlPoint(Vector2.Zero, PathType.Linear)); mergedHitObject.Path.ControlPoints.Add(new PathControlPoint(Vector2.Zero, PathType.LINEAR));
} }
// Merge all the selected hit objects into one slider path. // Merge all the selected hit objects into one slider path.
@ -350,7 +350,7 @@ namespace osu.Game.Rulesets.Osu.Edit
// Turn the last control point into a linear type if this is the first merging circle in a sequence, so the subsequent control points can be inherited path type. // Turn the last control point into a linear type if this is the first merging circle in a sequence, so the subsequent control points can be inherited path type.
if (!lastCircle) if (!lastCircle)
{ {
mergedHitObject.Path.ControlPoints.Last().Type = PathType.Linear; mergedHitObject.Path.ControlPoints.Last().Type = PathType.LINEAR;
} }
mergedHitObject.Path.ControlPoints.Add(new PathControlPoint(selectedMergeableObject.Position - mergedHitObject.Position)); mergedHitObject.Path.ControlPoints.Add(new PathControlPoint(selectedMergeableObject.Position - mergedHitObject.Position));

View File

@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
double IHasDistance.Distance => Duration * Velocity; double IHasDistance.Distance => Duration * Velocity;
SliderPath IHasPath.Path SliderPath IHasPath.Path
=> new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER); => new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER);
#endregion #endregion
} }

View File

@ -663,7 +663,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
assertObjectHasBanks(hitObjects[9], HitSampleInfo.BANK_DRUM, HitSampleInfo.BANK_NORMAL); assertObjectHasBanks(hitObjects[9], HitSampleInfo.BANK_DRUM, HitSampleInfo.BANK_NORMAL);
} }
void assertObjectHasBanks(HitObject hitObject, string normalBank, string? additionsBank = null) static void assertObjectHasBanks(HitObject hitObject, string normalBank, string? additionsBank = null)
{ {
Assert.AreEqual(normalBank, hitObject.Samples[0].Bank); Assert.AreEqual(normalBank, hitObject.Samples[0].Bank);
@ -808,14 +808,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
var first = ((IHasPath)decoded.HitObjects[0]).Path; var first = ((IHasPath)decoded.HitObjects[0]).Path;
Assert.That(first.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); Assert.That(first.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
Assert.That(first.ControlPoints[0].Type, Is.EqualTo(PathType.PerfectCurve)); Assert.That(first.ControlPoints[0].Type, Is.EqualTo(PathType.PERFECTCURVE));
Assert.That(first.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244))); Assert.That(first.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244)));
Assert.That(first.ControlPoints[1].Type, Is.EqualTo(null)); Assert.That(first.ControlPoints[1].Type, Is.EqualTo(null));
// ReSharper disable once HeuristicUnreachableCode // ReSharper disable once HeuristicUnreachableCode
// weird one, see https://youtrack.jetbrains.com/issue/RIDER-70159. // weird one, see https://youtrack.jetbrains.com/issue/RIDER-70159.
Assert.That(first.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3))); Assert.That(first.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3)));
Assert.That(first.ControlPoints[2].Type, Is.EqualTo(PathType.Bezier)); Assert.That(first.ControlPoints[2].Type, Is.EqualTo(PathType.BEZIER));
Assert.That(first.ControlPoints[3].Position, Is.EqualTo(new Vector2(68, 15))); Assert.That(first.ControlPoints[3].Position, Is.EqualTo(new Vector2(68, 15)));
Assert.That(first.ControlPoints[3].Type, Is.EqualTo(null)); Assert.That(first.ControlPoints[3].Type, Is.EqualTo(null));
Assert.That(first.ControlPoints[4].Position, Is.EqualTo(new Vector2(259, -132))); Assert.That(first.ControlPoints[4].Position, Is.EqualTo(new Vector2(259, -132)));
@ -827,7 +827,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var second = ((IHasPath)decoded.HitObjects[1]).Path; var second = ((IHasPath)decoded.HitObjects[1]).Path;
Assert.That(second.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); Assert.That(second.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
Assert.That(second.ControlPoints[0].Type, Is.EqualTo(PathType.PerfectCurve)); Assert.That(second.ControlPoints[0].Type, Is.EqualTo(PathType.PERFECTCURVE));
Assert.That(second.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244))); Assert.That(second.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244)));
Assert.That(second.ControlPoints[1].Type, Is.EqualTo(null)); Assert.That(second.ControlPoints[1].Type, Is.EqualTo(null));
Assert.That(second.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3))); Assert.That(second.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3)));
@ -837,14 +837,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
var third = ((IHasPath)decoded.HitObjects[2]).Path; var third = ((IHasPath)decoded.HitObjects[2]).Path;
Assert.That(third.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); Assert.That(third.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
Assert.That(third.ControlPoints[0].Type, Is.EqualTo(PathType.Bezier)); Assert.That(third.ControlPoints[0].Type, Is.EqualTo(PathType.BEZIER));
Assert.That(third.ControlPoints[1].Position, Is.EqualTo(new Vector2(0, 192))); Assert.That(third.ControlPoints[1].Position, Is.EqualTo(new Vector2(0, 192)));
Assert.That(third.ControlPoints[1].Type, Is.EqualTo(null)); Assert.That(third.ControlPoints[1].Type, Is.EqualTo(null));
Assert.That(third.ControlPoints[2].Position, Is.EqualTo(new Vector2(224, 192))); Assert.That(third.ControlPoints[2].Position, Is.EqualTo(new Vector2(224, 192)));
Assert.That(third.ControlPoints[2].Type, Is.EqualTo(null)); Assert.That(third.ControlPoints[2].Type, Is.EqualTo(null));
Assert.That(third.ControlPoints[3].Position, Is.EqualTo(new Vector2(224, 0))); Assert.That(third.ControlPoints[3].Position, Is.EqualTo(new Vector2(224, 0)));
Assert.That(third.ControlPoints[3].Type, Is.EqualTo(PathType.Bezier)); Assert.That(third.ControlPoints[3].Type, Is.EqualTo(PathType.BEZIER));
Assert.That(third.ControlPoints[4].Position, Is.EqualTo(new Vector2(224, -192))); Assert.That(third.ControlPoints[4].Position, Is.EqualTo(new Vector2(224, -192)));
Assert.That(third.ControlPoints[4].Type, Is.EqualTo(null)); Assert.That(third.ControlPoints[4].Type, Is.EqualTo(null));
Assert.That(third.ControlPoints[5].Position, Is.EqualTo(new Vector2(480, -192))); Assert.That(third.ControlPoints[5].Position, Is.EqualTo(new Vector2(480, -192)));
@ -856,7 +856,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var fourth = ((IHasPath)decoded.HitObjects[3]).Path; var fourth = ((IHasPath)decoded.HitObjects[3]).Path;
Assert.That(fourth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); Assert.That(fourth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
Assert.That(fourth.ControlPoints[0].Type, Is.EqualTo(PathType.Bezier)); Assert.That(fourth.ControlPoints[0].Type, Is.EqualTo(PathType.BEZIER));
Assert.That(fourth.ControlPoints[1].Position, Is.EqualTo(new Vector2(1, 1))); Assert.That(fourth.ControlPoints[1].Position, Is.EqualTo(new Vector2(1, 1)));
Assert.That(fourth.ControlPoints[1].Type, Is.EqualTo(null)); Assert.That(fourth.ControlPoints[1].Type, Is.EqualTo(null));
Assert.That(fourth.ControlPoints[2].Position, Is.EqualTo(new Vector2(2, 2))); Assert.That(fourth.ControlPoints[2].Position, Is.EqualTo(new Vector2(2, 2)));
@ -870,7 +870,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var fifth = ((IHasPath)decoded.HitObjects[4]).Path; var fifth = ((IHasPath)decoded.HitObjects[4]).Path;
Assert.That(fifth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); Assert.That(fifth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
Assert.That(fifth.ControlPoints[0].Type, Is.EqualTo(PathType.Bezier)); Assert.That(fifth.ControlPoints[0].Type, Is.EqualTo(PathType.BEZIER));
Assert.That(fifth.ControlPoints[1].Position, Is.EqualTo(new Vector2(1, 1))); Assert.That(fifth.ControlPoints[1].Position, Is.EqualTo(new Vector2(1, 1)));
Assert.That(fifth.ControlPoints[1].Type, Is.EqualTo(null)); Assert.That(fifth.ControlPoints[1].Type, Is.EqualTo(null));
Assert.That(fifth.ControlPoints[2].Position, Is.EqualTo(new Vector2(2, 2))); Assert.That(fifth.ControlPoints[2].Position, Is.EqualTo(new Vector2(2, 2)));
@ -881,7 +881,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.That(fifth.ControlPoints[4].Type, Is.EqualTo(null)); Assert.That(fifth.ControlPoints[4].Type, Is.EqualTo(null));
Assert.That(fifth.ControlPoints[5].Position, Is.EqualTo(new Vector2(4, 4))); Assert.That(fifth.ControlPoints[5].Position, Is.EqualTo(new Vector2(4, 4)));
Assert.That(fifth.ControlPoints[5].Type, Is.EqualTo(PathType.Bezier)); Assert.That(fifth.ControlPoints[5].Type, Is.EqualTo(PathType.BEZIER));
Assert.That(fifth.ControlPoints[6].Position, Is.EqualTo(new Vector2(5, 5))); Assert.That(fifth.ControlPoints[6].Position, Is.EqualTo(new Vector2(5, 5)));
Assert.That(fifth.ControlPoints[6].Type, Is.EqualTo(null)); Assert.That(fifth.ControlPoints[6].Type, Is.EqualTo(null));
@ -889,12 +889,12 @@ namespace osu.Game.Tests.Beatmaps.Formats
var sixth = ((IHasPath)decoded.HitObjects[5]).Path; var sixth = ((IHasPath)decoded.HitObjects[5]).Path;
Assert.That(sixth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); Assert.That(sixth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
Assert.That(sixth.ControlPoints[0].Type == PathType.Bezier); Assert.That(sixth.ControlPoints[0].Type == PathType.BEZIER);
Assert.That(sixth.ControlPoints[1].Position, Is.EqualTo(new Vector2(75, 145))); Assert.That(sixth.ControlPoints[1].Position, Is.EqualTo(new Vector2(75, 145)));
Assert.That(sixth.ControlPoints[1].Type == null); Assert.That(sixth.ControlPoints[1].Type == null);
Assert.That(sixth.ControlPoints[2].Position, Is.EqualTo(new Vector2(170, 75))); Assert.That(sixth.ControlPoints[2].Position, Is.EqualTo(new Vector2(170, 75)));
Assert.That(sixth.ControlPoints[2].Type == PathType.Bezier); Assert.That(sixth.ControlPoints[2].Type == PathType.BEZIER);
Assert.That(sixth.ControlPoints[3].Position, Is.EqualTo(new Vector2(300, 145))); Assert.That(sixth.ControlPoints[3].Position, Is.EqualTo(new Vector2(300, 145)));
Assert.That(sixth.ControlPoints[3].Type == null); Assert.That(sixth.ControlPoints[3].Type == null);
Assert.That(sixth.ControlPoints[4].Position, Is.EqualTo(new Vector2(410, 20))); Assert.That(sixth.ControlPoints[4].Position, Is.EqualTo(new Vector2(410, 20)));
@ -904,12 +904,12 @@ namespace osu.Game.Tests.Beatmaps.Formats
var seventh = ((IHasPath)decoded.HitObjects[6]).Path; var seventh = ((IHasPath)decoded.HitObjects[6]).Path;
Assert.That(seventh.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero)); Assert.That(seventh.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
Assert.That(seventh.ControlPoints[0].Type == PathType.PerfectCurve); Assert.That(seventh.ControlPoints[0].Type == PathType.PERFECTCURVE);
Assert.That(seventh.ControlPoints[1].Position, Is.EqualTo(new Vector2(75, 145))); Assert.That(seventh.ControlPoints[1].Position, Is.EqualTo(new Vector2(75, 145)));
Assert.That(seventh.ControlPoints[1].Type == null); Assert.That(seventh.ControlPoints[1].Type == null);
Assert.That(seventh.ControlPoints[2].Position, Is.EqualTo(new Vector2(170, 75))); Assert.That(seventh.ControlPoints[2].Position, Is.EqualTo(new Vector2(170, 75)));
Assert.That(seventh.ControlPoints[2].Type == PathType.PerfectCurve); Assert.That(seventh.ControlPoints[2].Type == PathType.PERFECTCURVE);
Assert.That(seventh.ControlPoints[3].Position, Is.EqualTo(new Vector2(300, 145))); Assert.That(seventh.ControlPoints[3].Position, Is.EqualTo(new Vector2(300, 145)));
Assert.That(seventh.ControlPoints[3].Type == null); Assert.That(seventh.ControlPoints[3].Type == null);
Assert.That(seventh.ControlPoints[4].Position, Is.EqualTo(new Vector2(410, 20))); Assert.That(seventh.ControlPoints[4].Position, Is.EqualTo(new Vector2(410, 20)));
@ -1016,7 +1016,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var controlPoints = ((IHasPath)decoded.HitObjects[0]).Path.ControlPoints; var controlPoints = ((IHasPath)decoded.HitObjects[0]).Path.ControlPoints;
Assert.That(controlPoints.Count, Is.EqualTo(6)); Assert.That(controlPoints.Count, Is.EqualTo(6));
Assert.That(controlPoints.Single(c => c.Type != null).Type, Is.EqualTo(PathType.Catmull)); Assert.That(controlPoints.Single(c => c.Type != null).Type, Is.EqualTo(PathType.CATMULL));
} }
} }
@ -1032,9 +1032,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
var controlPoints = ((IHasPath)decoded.HitObjects[0]).Path.ControlPoints; var controlPoints = ((IHasPath)decoded.HitObjects[0]).Path.ControlPoints;
Assert.That(controlPoints.Count, Is.EqualTo(4)); Assert.That(controlPoints.Count, Is.EqualTo(4));
Assert.That(controlPoints[0].Type, Is.EqualTo(PathType.Catmull)); Assert.That(controlPoints[0].Type, Is.EqualTo(PathType.CATMULL));
Assert.That(controlPoints[1].Type, Is.EqualTo(PathType.Catmull)); Assert.That(controlPoints[1].Type, Is.EqualTo(PathType.CATMULL));
Assert.That(controlPoints[2].Type, Is.EqualTo(PathType.Catmull)); Assert.That(controlPoints[2].Type, Is.EqualTo(PathType.CATMULL));
Assert.That(controlPoints[3].Type, Is.Null); Assert.That(controlPoints[3].Type, Is.Null);
} }
} }
@ -1051,7 +1051,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var controlPoints = ((IHasPath)decoded.HitObjects[0]).Path.ControlPoints; var controlPoints = ((IHasPath)decoded.HitObjects[0]).Path.ControlPoints;
Assert.That(controlPoints.Count, Is.EqualTo(4)); Assert.That(controlPoints.Count, Is.EqualTo(4));
Assert.That(controlPoints[0].Type, Is.EqualTo(PathType.Catmull)); Assert.That(controlPoints[0].Type, Is.EqualTo(PathType.CATMULL));
Assert.That(controlPoints[0].Position, Is.EqualTo(Vector2.Zero)); Assert.That(controlPoints[0].Position, Is.EqualTo(Vector2.Zero));
Assert.That(controlPoints[1].Type, Is.Null); Assert.That(controlPoints[1].Type, Is.Null);
Assert.That(controlPoints[1].Position, Is.Not.EqualTo(Vector2.Zero)); Assert.That(controlPoints[1].Position, Is.Not.EqualTo(Vector2.Zero));

View File

@ -77,7 +77,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
compareBeatmaps(decoded, decodedAfterEncode); compareBeatmaps(decoded, decodedAfterEncode);
ControlPointInfo removeLegacyControlPointTypes(ControlPointInfo controlPointInfo) static ControlPointInfo removeLegacyControlPointTypes(ControlPointInfo controlPointInfo)
{ {
// emulate non-legacy control points by cloning the non-legacy portion. // emulate non-legacy control points by cloning the non-legacy portion.
// the assertion is that the encoder can recreate this losslessly from hitobject data. // the assertion is that the encoder can recreate this losslessly from hitobject data.
@ -125,10 +125,10 @@ namespace osu.Game.Tests.Beatmaps.Formats
Position = new Vector2(0.6f), Position = new Vector2(0.6f),
Path = new SliderPath(new[] Path = new SliderPath(new[]
{ {
new PathControlPoint(Vector2.Zero, PathType.Bezier), new PathControlPoint(Vector2.Zero, PathType.BEZIER),
new PathControlPoint(new Vector2(0.5f)), new PathControlPoint(new Vector2(0.5f)),
new PathControlPoint(new Vector2(0.51f)), // This is actually on the same position as the previous one in legacy beatmaps (truncated to int). new PathControlPoint(new Vector2(0.51f)), // This is actually on the same position as the previous one in legacy beatmaps (truncated to int).
new PathControlPoint(new Vector2(1f), PathType.Bezier), new PathControlPoint(new Vector2(1f), PathType.BEZIER),
new PathControlPoint(new Vector2(2f)) new PathControlPoint(new Vector2(2f))
}) })
}, },

View File

@ -162,7 +162,7 @@ namespace osu.Game.Tests.Editing
{ {
new PathControlPoint(Vector2.Zero), new PathControlPoint(Vector2.Zero),
new PathControlPoint(Vector2.One), new PathControlPoint(Vector2.One),
new PathControlPoint(new Vector2(2), PathType.Bezier), new PathControlPoint(new Vector2(2), PathType.BEZIER),
new PathControlPoint(new Vector2(3)), new PathControlPoint(new Vector2(3)),
}, 50) }, 50)
}, },
@ -179,7 +179,7 @@ namespace osu.Game.Tests.Editing
StartTime = 2000, StartTime = 2000,
Path = new SliderPath(new[] Path = new SliderPath(new[]
{ {
new PathControlPoint(Vector2.Zero, PathType.Bezier), new PathControlPoint(Vector2.Zero, PathType.BEZIER),
new PathControlPoint(new Vector2(4)), new PathControlPoint(new Vector2(4)),
new PathControlPoint(new Vector2(5)), new PathControlPoint(new Vector2(5)),
}, 100) }, 100)

View File

@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.Editing
ControlPoints = ControlPoints =
{ {
new PathControlPoint(), new PathControlPoint(),
new PathControlPoint(new Vector2(100, 0), PathType.Bezier) new PathControlPoint(new Vector2(100, 0), PathType.BEZIER)
} }
} }
}; };

View File

@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Editing
new Slider new Slider
{ {
Position = new Vector2(128, 256), Position = new Vector2(128, 256),
Path = new SliderPath(PathType.Linear, new[] Path = new SliderPath(PathType.LINEAR, new[]
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(216, 0), new Vector2(216, 0),

View File

@ -114,23 +114,25 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
} }
[TestCase(PathType.Linear)] [TestCase(SplineType.Linear, null)]
[TestCase(PathType.Bezier)] [TestCase(SplineType.BSpline, null)]
[TestCase(PathType.Catmull)] [TestCase(SplineType.BSpline, 3)]
[TestCase(PathType.PerfectCurve)] [TestCase(SplineType.Catmull, null)]
public void TestSingleSegment(PathType type) [TestCase(SplineType.PerfectCurve, null)]
=> AddStep("create path", () => path.ControlPoints.AddRange(createSegment(type, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); public void TestSingleSegment(SplineType splineType, int? degree)
=> AddStep("create path", () => path.ControlPoints.AddRange(createSegment(new PathType { SplineType = splineType, Degree = degree }, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
[TestCase(PathType.Linear)] [TestCase(SplineType.Linear, null)]
[TestCase(PathType.Bezier)] [TestCase(SplineType.BSpline, null)]
[TestCase(PathType.Catmull)] [TestCase(SplineType.BSpline, 3)]
[TestCase(PathType.PerfectCurve)] [TestCase(SplineType.Catmull, null)]
public void TestMultipleSegment(PathType type) [TestCase(SplineType.PerfectCurve, null)]
public void TestMultipleSegment(SplineType splineType, int? degree)
{ {
AddStep("create path", () => AddStep("create path", () =>
{ {
path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero)); path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero));
path.ControlPoints.AddRange(createSegment(type, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); path.ControlPoints.AddRange(createSegment(new PathType { SplineType = splineType, Degree = degree }, new Vector2(0, 100), new Vector2(100), Vector2.Zero));
}); });
} }
@ -139,9 +141,9 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddStep("create path", () => AddStep("create path", () =>
{ {
path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(100, 0))); path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(100, 0)));
path.ControlPoints.AddRange(createSegment(PathType.Bezier, new Vector2(100, 0), new Vector2(150, 30), new Vector2(100, 100))); path.ControlPoints.AddRange(createSegment(PathType.BEZIER, new Vector2(100, 0), new Vector2(150, 30), new Vector2(100, 100)));
path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, new Vector2(100, 100), new Vector2(25, 50), Vector2.Zero)); path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, new Vector2(100, 100), new Vector2(25, 50), Vector2.Zero));
}); });
} }
@ -157,7 +159,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddStep("create path", () => AddStep("create path", () =>
{ {
path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(width / 2, height), new Vector2(width, 0))); path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(width / 2, height), new Vector2(width, 0)));
}); });
} }
@ -170,11 +172,11 @@ namespace osu.Game.Tests.Visual.Gameplay
switch (points) switch (points)
{ {
case 2: case 2:
path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100))); path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100)));
break; break;
case 4: case 4:
path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)));
break; break;
} }
}); });

View File

@ -88,7 +88,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
HitWindows = new HitWindows(), HitWindows = new HitWindows(),
StartTime = t += spacing, StartTime = t += spacing,
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }), Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, Vector2.UnitY * 200 }),
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, HitSampleInfo.BANK_SOFT) }, Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, HitSampleInfo.BANK_SOFT) },
}, },
}); });

View File

@ -52,59 +52,68 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
} }
[TestCase(PathType.Linear)] [TestCase(SplineType.Linear, null)]
[TestCase(PathType.Bezier)] [TestCase(SplineType.BSpline, null)]
[TestCase(PathType.Catmull)] [TestCase(SplineType.BSpline, 3)]
[TestCase(PathType.PerfectCurve)] [TestCase(SplineType.Catmull, null)]
public void TestSingleSegment(PathType type) [TestCase(SplineType.PerfectCurve, null)]
=> AddStep("create path", () => path.ControlPoints.AddRange(createSegment(type, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); public void TestSingleSegment(SplineType splineType, int? degree)
=> AddStep("create path", () => path.ControlPoints.AddRange(createSegment(
new PathType { SplineType = splineType, Degree = degree },
Vector2.Zero,
new Vector2(0, 100),
new Vector2(100),
new Vector2(0, 200),
new Vector2(200)
)));
[TestCase(PathType.Linear)] [TestCase(SplineType.Linear, null)]
[TestCase(PathType.Bezier)] [TestCase(SplineType.BSpline, null)]
[TestCase(PathType.Catmull)] [TestCase(SplineType.BSpline, 3)]
[TestCase(PathType.PerfectCurve)] [TestCase(SplineType.Catmull, null)]
public void TestMultipleSegment(PathType type) [TestCase(SplineType.PerfectCurve, null)]
public void TestMultipleSegment(SplineType splineType, int? degree)
{ {
AddStep("create path", () => AddStep("create path", () =>
{ {
path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero)); path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero));
path.ControlPoints.AddRange(createSegment(type, new Vector2(0, 100), new Vector2(100), Vector2.Zero)); path.ControlPoints.AddRange(createSegment(new PathType { SplineType = splineType, Degree = degree }, new Vector2(0, 100), new Vector2(100), Vector2.Zero));
}); });
} }
[Test] [Test]
public void TestAddControlPoint() public void TestAddControlPoint()
{ {
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100)))); AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100))));
AddStep("add point", () => path.ControlPoints.Add(new PathControlPoint { Position = new Vector2(100) })); AddStep("add point", () => path.ControlPoints.Add(new PathControlPoint { Position = new Vector2(100) }));
} }
[Test] [Test]
public void TestInsertControlPoint() public void TestInsertControlPoint()
{ {
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(100)))); AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(100))));
AddStep("insert point", () => path.ControlPoints.Insert(1, new PathControlPoint { Position = new Vector2(0, 100) })); AddStep("insert point", () => path.ControlPoints.Insert(1, new PathControlPoint { Position = new Vector2(0, 100) }));
} }
[Test] [Test]
public void TestRemoveControlPoint() public void TestRemoveControlPoint()
{ {
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
AddStep("remove second point", () => path.ControlPoints.RemoveAt(1)); AddStep("remove second point", () => path.ControlPoints.RemoveAt(1));
} }
[Test] [Test]
public void TestChangePathType() public void TestChangePathType()
{ {
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
AddStep("change type to bezier", () => path.ControlPoints[0].Type = PathType.Bezier); AddStep("change type to bezier", () => path.ControlPoints[0].Type = PathType.BEZIER);
} }
[Test] [Test]
public void TestAddSegmentByChangingType() public void TestAddSegmentByChangingType()
{ {
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)))); AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))));
AddStep("change second point type to bezier", () => path.ControlPoints[1].Type = PathType.Bezier); AddStep("change second point type to bezier", () => path.ControlPoints[1].Type = PathType.BEZIER);
} }
[Test] [Test]
@ -112,8 +121,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddStep("create path", () => AddStep("create path", () =>
{ {
path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)));
path.ControlPoints[1].Type = PathType.Bezier; path.ControlPoints[1].Type = PathType.BEZIER;
}); });
AddStep("change second point type to null", () => path.ControlPoints[1].Type = null); AddStep("change second point type to null", () => path.ControlPoints[1].Type = null);
@ -124,8 +133,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddStep("create path", () => AddStep("create path", () =>
{ {
path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)));
path.ControlPoints[1].Type = PathType.Bezier; path.ControlPoints[1].Type = PathType.BEZIER;
}); });
AddStep("remove second point", () => path.ControlPoints.RemoveAt(1)); AddStep("remove second point", () => path.ControlPoints.RemoveAt(1));
@ -140,11 +149,11 @@ namespace osu.Game.Tests.Visual.Gameplay
switch (points) switch (points)
{ {
case 2: case 2:
path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100))); path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100)));
break; break;
case 4: case 4:
path.ControlPoints.AddRange(createSegment(PathType.PerfectCurve, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0))); path.ControlPoints.AddRange(createSegment(PathType.PERFECTCURVE, Vector2.Zero, new Vector2(0, 100), new Vector2(100), new Vector2(100, 0)));
break; break;
} }
}); });
@ -153,35 +162,35 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestLengthenLastSegment() public void TestLengthenLastSegment()
{ {
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
AddStep("lengthen last segment", () => path.ExpectedDistance.Value = 300); AddStep("lengthen last segment", () => path.ExpectedDistance.Value = 300);
} }
[Test] [Test]
public void TestShortenLastSegment() public void TestShortenLastSegment()
{ {
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
AddStep("shorten last segment", () => path.ExpectedDistance.Value = 150); AddStep("shorten last segment", () => path.ExpectedDistance.Value = 150);
} }
[Test] [Test]
public void TestShortenFirstSegment() public void TestShortenFirstSegment()
{ {
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
AddStep("shorten first segment", () => path.ExpectedDistance.Value = 50); AddStep("shorten first segment", () => path.ExpectedDistance.Value = 50);
} }
[Test] [Test]
public void TestShortenToZeroLength() public void TestShortenToZeroLength()
{ {
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
AddStep("shorten to 0 length", () => path.ExpectedDistance.Value = 0); AddStep("shorten to 0 length", () => path.ExpectedDistance.Value = 0);
} }
[Test] [Test]
public void TestShortenToNegativeLength() public void TestShortenToNegativeLength()
{ {
AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.Linear, Vector2.Zero, new Vector2(0, 100), new Vector2(100)))); AddStep("create path", () => path.ControlPoints.AddRange(createSegment(PathType.LINEAR, Vector2.Zero, new Vector2(0, 100), new Vector2(100))));
AddStep("shorten to -10 length", () => path.ExpectedDistance.Value = -10); AddStep("shorten to -10 length", () => path.ExpectedDistance.Value = -10);
} }
@ -197,7 +206,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}; };
double[] distances = { 100d, 200d, 300d }; double[] distances = { 100d, 200d, 300d };
AddStep("create path", () => path.ControlPoints.AddRange(positions.Select(p => new PathControlPoint(p, PathType.Linear)))); AddStep("create path", () => path.ControlPoints.AddRange(positions.Select(p => new PathControlPoint(p, PathType.LINEAR))));
AddAssert("segment ends are correct", () => path.GetSegmentEnds(), () => Is.EqualTo(distances.Select(d => d / 300))); AddAssert("segment ends are correct", () => path.GetSegmentEnds(), () => Is.EqualTo(distances.Select(d => d / 300)));
AddAssert("segment end positions recovered", () => path.GetSegmentEnds().Select(p => path.PositionAt(p)), () => Is.EqualTo(positions.Skip(1))); AddAssert("segment end positions recovered", () => path.GetSegmentEnds().Select(p => path.PositionAt(p)), () => Is.EqualTo(positions.Skip(1)));

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -437,7 +438,7 @@ namespace osu.Game.Beatmaps.Formats
// Explicit segments have a new format in which the type is injected into the middle of the control point string. // Explicit segments have a new format in which the type is injected into the middle of the control point string.
// To preserve compatibility with osu-stable as much as possible, explicit segments with the same type are converted to use implicit segments by duplicating the control point. // To preserve compatibility with osu-stable as much as possible, explicit segments with the same type are converted to use implicit segments by duplicating the control point.
// One exception are consecutive perfect curves, which aren't supported in osu!stable and can lead to decoding issues if encoded as implicit segments // One exception are consecutive perfect curves, which aren't supported in osu!stable and can lead to decoding issues if encoded as implicit segments
bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PerfectCurve; bool needsExplicitSegment = point.Type != lastType || point.Type == PathType.PERFECTCURVE;
// Another exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable. // Another exception to this is when the last two control points of the last segment were duplicated. This is not a scenario supported by osu!stable.
// Lazer does not add implicit segments for the last two control points of _any_ explicit segment, so an explicit segment is forced in order to maintain consistency with the decoder. // Lazer does not add implicit segments for the last two control points of _any_ explicit segment, so an explicit segment is forced in order to maintain consistency with the decoder.
@ -455,19 +456,23 @@ namespace osu.Game.Beatmaps.Formats
{ {
switch (point.Type) switch (point.Type)
{ {
case PathType.Bezier: case { SplineType: SplineType.BSpline, Degree: > 0 }:
writer.Write($"B{point.Type.Value.Degree}|");
break;
case { SplineType: SplineType.BSpline, Degree: <= 0 }:
writer.Write("B|"); writer.Write("B|");
break; break;
case PathType.Catmull: case { SplineType: SplineType.Catmull }:
writer.Write("C|"); writer.Write("C|");
break; break;
case PathType.PerfectCurve: case { SplineType: SplineType.PerfectCurve }:
writer.Write("P|"); writer.Write("P|");
break; break;
case PathType.Linear: case { SplineType: SplineType.Linear }:
writer.Write("L|"); writer.Write("L|");
break; break;
} }

View File

@ -78,10 +78,10 @@ namespace osu.Game.Database
// wherein the last control point of an otherwise-single-segment slider path has a different type than previous, // wherein the last control point of an otherwise-single-segment slider path has a different type than previous,
// which would lead to sliders being mangled when exported back to stable. // which would lead to sliders being mangled when exported back to stable.
// normally, that would be handled by the `BezierConverter.ConvertToModernBezier()` call below, // normally, that would be handled by the `BezierConverter.ConvertToModernBezier()` call below,
// which outputs a slider path containing only Bezier control points, // which outputs a slider path containing only BEZIER control points,
// but a non-inherited last control point is (rightly) not considered to be starting a new segment, // but a non-inherited last control point is (rightly) not considered to be starting a new segment,
// therefore it would fail to clear the `CountSegments() <= 1` check. // therefore it would fail to clear the `CountSegments() <= 1` check.
// by clearing explicitly we both fix the issue and avoid unnecessary conversions to Bezier. // by clearing explicitly we both fix the issue and avoid unnecessary conversions to BEZIER.
if (hasPath.Path.ControlPoints.Count > 1) if (hasPath.Path.ControlPoints.Count > 1)
hasPath.Path.ControlPoints[^1].Type = null; hasPath.Path.ControlPoints[^1].Type = null;

View File

@ -68,26 +68,26 @@ namespace osu.Game.Rulesets.Objects
// The current vertex ends the segment // The current vertex ends the segment
var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1);
var segmentType = controlPoints[start].Type ?? PathType.Linear; var segmentType = controlPoints[start].Type ?? PathType.LINEAR;
switch (segmentType) switch (segmentType)
{ {
case PathType.Catmull: case { SplineType: SplineType.Catmull }:
result.AddRange(from segment in ConvertCatmullToBezierAnchors(segmentVertices) from v in segment select v + position); result.AddRange(from segment in ConvertCatmullToBezierAnchors(segmentVertices) from v in segment select v + position);
break; break;
case PathType.Linear: case { SplineType: SplineType.Linear }:
result.AddRange(from segment in ConvertLinearToBezierAnchors(segmentVertices) from v in segment select v + position); result.AddRange(from segment in ConvertLinearToBezierAnchors(segmentVertices) from v in segment select v + position);
break; break;
case PathType.PerfectCurve: case { SplineType: SplineType.PerfectCurve }:
result.AddRange(ConvertCircleToBezierAnchors(segmentVertices).Select(v => v + position)); result.AddRange(ConvertCircleToBezierAnchors(segmentVertices).Select(v => v + position));
break; break;
default: default:
if (segmentType.Degree != null)
throw new NotImplementedException("BSpline conversion of arbitrary degree is not implemented.");
foreach (Vector2 v in segmentVertices) foreach (Vector2 v in segmentVertices)
{ {
result.Add(v + position); result.Add(v + position);
@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Objects
} }
/// <summary> /// <summary>
/// Converts a path of control points to an identical path using only Bezier type control points. /// Converts a path of control points to an identical path using only BEZIER type control points.
/// </summary> /// </summary>
/// <param name="controlPoints">The control points of the path.</param> /// <param name="controlPoints">The control points of the path.</param>
/// <returns>The list of bezier control points.</returns> /// <returns>The list of bezier control points.</returns>
@ -124,38 +124,38 @@ namespace osu.Game.Rulesets.Objects
// The current vertex ends the segment // The current vertex ends the segment
var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1);
var segmentType = controlPoints[start].Type ?? PathType.Linear; var segmentType = controlPoints[start].Type ?? PathType.LINEAR;
switch (segmentType) switch (segmentType)
{ {
case PathType.Catmull: case { SplineType: SplineType.Catmull }:
foreach (var segment in ConvertCatmullToBezierAnchors(segmentVertices)) foreach (var segment in ConvertCatmullToBezierAnchors(segmentVertices))
{ {
for (int j = 0; j < segment.Length - 1; j++) for (int j = 0; j < segment.Length - 1; j++)
{ {
result.Add(new PathControlPoint(segment[j], j == 0 ? PathType.Bezier : null)); result.Add(new PathControlPoint(segment[j], j == 0 ? PathType.BEZIER : null));
} }
} }
break; break;
case PathType.Linear: case { SplineType: SplineType.Linear }:
foreach (var segment in ConvertLinearToBezierAnchors(segmentVertices)) foreach (var segment in ConvertLinearToBezierAnchors(segmentVertices))
{ {
for (int j = 0; j < segment.Length - 1; j++) for (int j = 0; j < segment.Length - 1; j++)
{ {
result.Add(new PathControlPoint(segment[j], j == 0 ? PathType.Bezier : null)); result.Add(new PathControlPoint(segment[j], j == 0 ? PathType.BEZIER : null));
} }
} }
break; break;
case PathType.PerfectCurve: case { SplineType: SplineType.PerfectCurve }:
var circleResult = ConvertCircleToBezierAnchors(segmentVertices); var circleResult = ConvertCircleToBezierAnchors(segmentVertices);
for (int j = 0; j < circleResult.Length - 1; j++) for (int j = 0; j < circleResult.Length - 1; j++)
{ {
result.Add(new PathControlPoint(circleResult[j], j == 0 ? PathType.Bezier : null)); result.Add(new PathControlPoint(circleResult[j], j == 0 ? PathType.BEZIER : null));
} }
break; break;
@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Objects
default: default:
for (int j = 0; j < segmentVertices.Length - 1; j++) for (int j = 0; j < segmentVertices.Length - 1; j++)
{ {
result.Add(new PathControlPoint(segmentVertices[j], j == 0 ? PathType.Bezier : null)); result.Add(new PathControlPoint(segmentVertices[j], j == 0 ? segmentType : null));
} }
break; break;

View File

@ -224,16 +224,18 @@ namespace osu.Game.Rulesets.Objects.Legacy
{ {
default: default:
case 'C': case 'C':
return PathType.Catmull; return new PathType(SplineType.Catmull);
case 'B': case 'B':
return PathType.Bezier; if (input.Length > 1 && int.TryParse(input.Substring(1), out int degree) && degree > 0)
return new PathType { SplineType = SplineType.BSpline, Degree = degree };
return new PathType(SplineType.BSpline);
case 'L': case 'L':
return PathType.Linear; return new PathType(SplineType.Linear);
case 'P': case 'P':
return PathType.PerfectCurve; return new PathType(SplineType.PerfectCurve);
} }
} }
@ -320,14 +322,14 @@ namespace osu.Game.Rulesets.Objects.Legacy
readPoint(endPoint, offset, out vertices[^1]); readPoint(endPoint, offset, out vertices[^1]);
// Edge-case rules (to match stable). // Edge-case rules (to match stable).
if (type == PathType.PerfectCurve) if (type == PathType.PERFECTCURVE)
{ {
if (vertices.Length != 3) if (vertices.Length != 3)
type = PathType.Bezier; type = PathType.BEZIER;
else if (isLinear(vertices)) else if (isLinear(vertices))
{ {
// osu-stable special-cased colinear perfect curves to a linear path // osu-stable special-cased colinear perfect curves to a linear path
type = PathType.Linear; type = PathType.LINEAR;
} }
} }
@ -349,10 +351,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (vertices[endIndex].Position != vertices[endIndex - 1].Position) if (vertices[endIndex].Position != vertices[endIndex - 1].Position)
continue; continue;
// Legacy Catmull sliders don't support multiple segments, so adjacent Catmull segments should be treated as a single one. // Legacy CATMULL sliders don't support multiple segments, so adjacent CATMULL segments should be treated as a single one.
// Importantly, this is not applied to the first control point, which may duplicate the slider path's position // Importantly, this is not applied to the first control point, which may duplicate the slider path's position
// resulting in a duplicate (0,0) control point in the resultant list. // resulting in a duplicate (0,0) control point in the resultant list.
if (type == PathType.Catmull && endIndex > 1 && FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION) if (type == PathType.CATMULL && endIndex > 1 && FormatVersion < LegacyBeatmapEncoder.FIRST_LAZER_VERSION)
continue; continue;
// The last control point of each segment is not allowed to start a new implicit segment. // The last control point of each segment is not allowed to start a new implicit segment.

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using Microsoft.Toolkit.HighPerformance;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Caching; using osu.Framework.Caching;
@ -260,7 +261,7 @@ namespace osu.Game.Rulesets.Objects
// The current vertex ends the segment // The current vertex ends the segment
var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1); var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1);
var segmentType = ControlPoints[start].Type ?? PathType.Linear; var segmentType = ControlPoints[start].Type ?? PathType.LINEAR;
// No need to calculate path when there is only 1 vertex // No need to calculate path when there is only 1 vertex
if (segmentVertices.Length == 1) if (segmentVertices.Length == 1)
@ -288,12 +289,12 @@ namespace osu.Game.Rulesets.Objects
private List<Vector2> calculateSubPath(ReadOnlySpan<Vector2> subControlPoints, PathType type) private List<Vector2> calculateSubPath(ReadOnlySpan<Vector2> subControlPoints, PathType type)
{ {
switch (type) switch (type.SplineType)
{ {
case PathType.Linear: case SplineType.Linear:
return PathApproximator.ApproximateLinear(subControlPoints); return PathApproximator.ApproximateLinear(subControlPoints);
case PathType.PerfectCurve: case SplineType.PerfectCurve:
if (subControlPoints.Length != 3) if (subControlPoints.Length != 3)
break; break;
@ -305,11 +306,11 @@ namespace osu.Game.Rulesets.Objects
return subPath; return subPath;
case PathType.Catmull: case SplineType.Catmull:
return PathApproximator.ApproximateCatmull(subControlPoints); return PathApproximator.ApproximateCatmull(subControlPoints);
} }
return PathApproximator.ApproximateBezier(subControlPoints); return PathApproximator.ApproximateBSpline(subControlPoints, type.Degree ?? subControlPoints.Length);
} }
private void calculateLength() private void calculateLength()

View File

@ -29,11 +29,11 @@ namespace osu.Game.Rulesets.Objects
{ {
var controlPoints = sliderPath.ControlPoints; var controlPoints = sliderPath.ControlPoints;
var inheritedLinearPoints = controlPoints.Where(p => sliderPath.PointsInSegment(p)[0].Type == PathType.Linear && p.Type is null).ToList(); var inheritedLinearPoints = controlPoints.Where(p => sliderPath.PointsInSegment(p)[0].Type == PathType.LINEAR && p.Type is null).ToList();
// Inherited points after a linear point, as well as the first control point if it inherited, // Inherited points after a linear point, as well as the first control point if it inherited,
// should be treated as linear points, so their types are temporarily changed to linear. // should be treated as linear points, so their types are temporarily changed to linear.
inheritedLinearPoints.ForEach(p => p.Type = PathType.Linear); inheritedLinearPoints.ForEach(p => p.Type = PathType.LINEAR);
double[] segmentEnds = sliderPath.GetSegmentEnds().ToArray(); double[] segmentEnds = sliderPath.GetSegmentEnds().ToArray();
@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Objects
inheritedLinearPoints.ForEach(p => p.Type = null); inheritedLinearPoints.ForEach(p => p.Type = null);
// Recalculate middle perfect curve control points at the end of the slider path. // Recalculate middle perfect curve control points at the end of the slider path.
if (controlPoints.Count >= 3 && controlPoints[^3].Type == PathType.PerfectCurve && controlPoints[^2].Type is null && segmentEnds.Any()) if (controlPoints.Count >= 3 && controlPoints[^3].Type == PathType.PERFECTCURVE && controlPoints[^2].Type is null && segmentEnds.Any())
{ {
double lastSegmentStart = segmentEnds.Length > 1 ? segmentEnds[^2] : 0; double lastSegmentStart = segmentEnds.Length > 1 ? segmentEnds[^2] : 0;
double lastSegmentEnd = segmentEnds[^1]; double lastSegmentEnd = segmentEnds[^1];

View File

@ -1,13 +1,59 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
namespace osu.Game.Rulesets.Objects.Types namespace osu.Game.Rulesets.Objects.Types
{ {
public enum PathType public enum SplineType
{ {
Catmull, Catmull,
Bezier, BSpline,
Linear, Linear,
PerfectCurve PerfectCurve
} }
public struct PathType
{
public static readonly PathType CATMULL = new PathType(SplineType.Catmull);
public static readonly PathType BEZIER = new PathType(SplineType.BSpline);
public static readonly PathType LINEAR = new PathType(SplineType.Linear);
public static readonly PathType PERFECTCURVE = new PathType(SplineType.PerfectCurve);
/// <summary>
/// The type of the spline that should be used to interpret the control points of the path.
/// </summary>
public SplineType SplineType { get; init; }
/// <summary>
/// The degree of a BSpline. Unused if <see cref="SplineType"/> is not <see cref="SplineType.BSpline"/>.
/// Null means the degree is equal to the number of control points, 1 means linear, 2 means quadratic, etc.
/// </summary>
public int? Degree { get; init; }
public PathType(SplineType splineType)
{
SplineType = splineType;
Degree = null;
}
public override int GetHashCode()
=> HashCode.Combine(SplineType, Degree);
public override bool Equals(object? obj)
=> obj is PathType pathType && this == pathType;
public static bool operator ==(PathType a, PathType b)
=> a.SplineType == b.SplineType && a.Degree == b.Degree;
public static bool operator !=(PathType a, PathType b)
=> a.SplineType != b.SplineType || a.Degree != b.Degree;
public static PathType BSpline(int degree)
{
Debug.Assert(degree > 0);
return new PathType { SplineType = SplineType.BSpline, Degree = degree };
}
}
} }

View File

@ -246,13 +246,13 @@ namespace osu.Game.Screens.Play.HUD
barPath = new SliderPath(new[] barPath = new SliderPath(new[]
{ {
new PathControlPoint(new Vector2(0, 0), PathType.Linear), new PathControlPoint(new Vector2(0, 0), PathType.LINEAR),
new PathControlPoint(new Vector2(curveStart - curve_smoothness, 0), PathType.Bezier), new PathControlPoint(new Vector2(curveStart - curve_smoothness, 0), PathType.BEZIER),
new PathControlPoint(new Vector2(curveStart, 0)), new PathControlPoint(new Vector2(curveStart, 0)),
new PathControlPoint(new Vector2(curveStart, 0) + diagonalDir * curve_smoothness, PathType.Linear), new PathControlPoint(new Vector2(curveStart, 0) + diagonalDir * curve_smoothness, PathType.LINEAR),
new PathControlPoint(new Vector2(curveEnd, BarHeight.Value) - diagonalDir * curve_smoothness, PathType.Bezier), new PathControlPoint(new Vector2(curveEnd, BarHeight.Value) - diagonalDir * curve_smoothness, PathType.BEZIER),
new PathControlPoint(new Vector2(curveEnd, BarHeight.Value)), new PathControlPoint(new Vector2(curveEnd, BarHeight.Value)),
new PathControlPoint(new Vector2(curveEnd + curve_smoothness, BarHeight.Value), PathType.Linear), new PathControlPoint(new Vector2(curveEnd + curve_smoothness, BarHeight.Value), PathType.LINEAR),
new PathControlPoint(new Vector2(barLength, BarHeight.Value)), new PathControlPoint(new Vector2(barLength, BarHeight.Value)),
}); });