Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to cycle between control point types #27035

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,111 @@ public void TestDragControlPointPathAfterChangingType()
assertControlPointType(3, PathType.BEZIER);
}

[Test]
public void TestCyclePathTypeChangesPathType()
{
moveMouseToControlPoint(0);
AddStep("shift + click", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Click(MouseButton.Left);
InputManager.ReleaseKey(Key.ShiftLeft);
});
assertControlPointType(0, PathType.PERFECT_CURVE, false);
}

[Test]
public void TestCyclePathTypeDoesNotBreakPreviousPerfectCurve()
{
moveMouseToControlPoint(2);

for (int iteration = 0; iteration < 6; iteration++)
{
AddStep("shift + click", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Click(MouseButton.Left);
InputManager.ReleaseKey(Key.ShiftLeft);
});
}

assertControlPointType(0, PathType.PERFECT_CURVE, true);
}

[Test]
public void TestCyclePathTypeCanBecomeInherited()
{
moveMouseToControlPoint(0);
AddStep("shift + click", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Click(MouseButton.Left);
InputManager.ReleaseKey(Key.ShiftLeft);
});
assertControlPointType(0, PathType.LINEAR, true);
moveMouseToControlPoint(2);

for (int iteration = 0; iteration < 3; iteration++)
{
AddStep("shift + click", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Click(MouseButton.Left);
InputManager.ReleaseKey(Key.ShiftLeft);
});
}

assertControlPointType(2, null, true);
}

[Test]
public void TestCyclePathTypeBSpline()
{
AddStep("change point 0 type to bezier", () => slider.Path.ControlPoints[0].Type = PathType.BEZIER);
AddStep("change point 2 to inherited", () => slider.Path.ControlPoints[2].Type = null);
moveMouseToControlPoint(0);
AddStep("shift + click", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Click(MouseButton.Left);
InputManager.ReleaseKey(Key.ShiftLeft);
});
assertControlPointType(0, PathType.BSpline(4), false);
AddStep("change point 0 type to bezier", () => slider.Path.ControlPoints[0].Type = PathType.BEZIER);
AddStep("add point", () => slider.Path.ControlPoints.Add(new PathControlPoint(new Vector2(500, 10))));
AddStep("shift + click", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Click(MouseButton.Left);
InputManager.ReleaseKey(Key.ShiftLeft);
});
assertControlPointType(0, PathType.BSpline(4), true);
}

[Test]
public void TestCycleControlPointAvoidsInvalidPerfectCurve()
{
AddStep("change point 0 type to bezier", () => slider.Path.ControlPoints[0].Type = PathType.BEZIER);
AddStep("change point 2 to inherited", () => slider.Path.ControlPoints[2].Type = null);
moveMouseToControlPoint(0);
AddStep("shift + click", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Click(MouseButton.Left);
InputManager.ReleaseKey(Key.ShiftLeft);
});
assertControlPointType(0, PathType.PERFECT_CURVE, false);
AddStep("change point 0 type to bezier", () => slider.Path.ControlPoints[0].Type = PathType.BEZIER);
AddStep("change point 2 to perfect curve", () => slider.Path.ControlPoints[2].Type = PathType.PERFECT_CURVE);
AddStep("shift + click", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Click(MouseButton.Left);
InputManager.ReleaseKey(Key.ShiftLeft);
});
assertControlPointType(0, PathType.PERFECT_CURVE, true);
}

private void addMovementStep(Vector2 relativePosition)
{
AddStep($"move mouse to {relativePosition}", () =>
Expand All @@ -344,7 +449,12 @@ private void moveMouseToControlPoint(int index)
});
}

private void assertControlPointType(int index, PathType type) => AddAssert($"control point {index} is {type}", () => slider.Path.ControlPoints[index].Type == type);
private void assertControlPointType(int index, PathType type) => assertControlPointType(index, type, true);

private void assertControlPointType(int index, PathType? type, bool shouldBeType)
{
AddAssert($"control point {index} is {(shouldBeType ? "" : "not ")}{type?.ToString() ?? "Inherited"}", () => (slider.Path.ControlPoints[index].Type == type) == shouldBeType);
}

private void assertControlPointPosition(int index, Vector2 position) =>
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, slider.Path.ControlPoints[index].Position, 1));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,57 @@ private void selectionRequested(PathControlPointPiece<T> piece, MouseButtonEvent
{
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
piece.IsSelected.Toggle();
else if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ShiftPressed)
{
PathType? newPathtype = cyclePathType(piece);
updatePathType(piece, newPathtype);
EnsureValidPathTypes();
}
else
SetSelectionTo(piece.ControlPoint);
}

/// <summary>
/// Cycle between relevant <see cref="PathType"/>s for this <see cref="PathControlPoint"/>.
/// </summary>
/// <param name="piece"></param>
/// <returns><see cref="PathType"/> to change to</returns>
private PathType? cyclePathType(PathControlPointPiece<T> piece)
{
var oldPathType = piece.ControlPoint.Type;

// Only allow changing to perfect curve if there's less than 2 inherited points in the segment
var pointsInSegment = hitObject.Path.PointsInSegment(piece.ControlPoint);
if (pointsInSegment.Count <= 3 && oldPathType == PathType.BEZIER)
return PathType.PERFECT_CURVE;

// Only allow change to B-Spline if it looks different than Bezier
if (pointsInSegment.Count >= 6 && oldPathType == PathType.BEZIER)
return PathType.BSpline(4);

// Allow changing to inherited only if it there exists a segment behind the point which isn't a finished perfect curve
int? previousSegmentIndex = hitObject.Path.GetPreviousSegmentStarterIndex(piece.ControlPoint);
int currentSegmentIndex = hitObject.Path.ControlPoints.IndexOf(piece.ControlPoint);
bool isPreviousSegmentPerfectCurve = previousSegmentIndex != null
&& hitObject.Path.ControlPoints[(int)previousSegmentIndex].Type == PathType.PERFECT_CURVE;
bool doesPreviousSegmentHaveExactlyTwoPoints = previousSegmentIndex == currentSegmentIndex - 2;
bool isPreviousSegmentAFinishedPerfectCurve = isPreviousSegmentPerfectCurve && doesPreviousSegmentHaveExactlyTwoPoints;
if (!isPreviousSegmentAFinishedPerfectCurve && currentSegmentIndex != 0 && oldPathType == PathType.CATMULL)
return null;

PathType? newPathType = oldPathType switch
{
var _ when oldPathType == null => PathType.BEZIER,
var _ when oldPathType == PathType.BEZIER => PathType.LINEAR,
var _ when oldPathType == PathType.PERFECT_CURVE => PathType.LINEAR,
var _ when oldPathType == PathType.LINEAR => PathType.CATMULL,
var _ when oldPathType == PathType.CATMULL => PathType.BEZIER,
var _ when ((PathType)oldPathType).Type == SplineType.BSpline => PathType.LINEAR,
_ => (PathType)oldPathType
};
return newPathType;
}

/// <summary>
/// Attempts to set the given control point piece to the given path type.
/// If that would fail, try to change the path such that it instead succeeds
Expand Down
15 changes: 15 additions & 0 deletions osu.Game/Rulesets/Objects/SliderPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,21 @@ public List<PathControlPoint> PointsInSegment(PathControlPoint controlPoint)
return pointsInCurrentSegment;
}

public int? GetPreviousSegmentStarterIndex(PathControlPoint controlPoint)
{
int currentSegmentIndex = ControlPoints.IndexOf(controlPoint);
if (currentSegmentIndex <= 0)
return null;

for (int i = currentSegmentIndex - 1; i >= 0; i--)
{
if (ControlPoints[i].Type != null)
return i;
}

return 0;
}

/// <summary>
/// Returns the progress values at which (control point) segments of the path end.
/// Ranges from 0 (beginning of the path) to 1 (end of the path) to infinity (beyond the end of the path).
Expand Down
Loading