Skip to content

Commit

Permalink
Merge pull request #25743 from peppy/merge-everything-at-once
Browse files Browse the repository at this point in the history
Merge everything at once
  • Loading branch information
peppy authored Dec 13, 2023
2 parents a61dfa4 + c2d3dcd commit ec6200b
Show file tree
Hide file tree
Showing 35 changed files with 349 additions and 117 deletions.
2 changes: 1 addition & 1 deletion osu.Android.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.1201.1" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.1213.0" />
</ItemGroup>
<PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,9 @@ public void TestSliderDrawingCurve()
AddStep("release left button", () => InputManager.ReleaseButton(MouseButton.Left));

assertPlaced(true);
assertLength(760, tolerance: 10);
assertLength(808, tolerance: 10);
assertControlPointCount(5);
assertControlPointType(0, PathType.BSpline(3));
assertControlPointType(0, PathType.BSpline(4));
assertControlPointType(1, null);
assertControlPointType(2, null);
assertControlPointType(3, null);
Expand All @@ -337,9 +337,9 @@ public void TestSliderDrawingLinear()
assertPlaced(true);
assertLength(600, tolerance: 10);
assertControlPointCount(4);
assertControlPointType(0, PathType.LINEAR);
assertControlPointType(1, null);
assertControlPointType(2, null);
assertControlPointType(0, PathType.BSpline(4));
assertControlPointType(1, PathType.BSpline(4));
assertControlPointType(2, PathType.BSpline(4));
assertControlPointType(3, null);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ public MenuItem[] ContextMenuItems
curveTypeItems.Add(createMenuItemForPathType(PathType.LINEAR));
curveTypeItems.Add(createMenuItemForPathType(PathType.PERFECT_CURVE));
curveTypeItems.Add(createMenuItemForPathType(PathType.BEZIER));
curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(3)));
curveTypeItems.Add(createMenuItemForPathType(PathType.BSpline(4)));

if (selectedPieces.Any(piece => piece.ControlPoint.Type?.Type == SplineType.Catmull))
curveTypeItems.Add(createMenuItemForPathType(PathType.CATMULL));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#nullable disable

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
Expand Down Expand Up @@ -41,15 +42,18 @@ public partial class SliderPlacementBlueprint : PlacementBlueprint
private int currentSegmentLength;

[Resolved(CanBeNull = true)]
[CanBeNull]
private IPositionSnapProvider positionSnapProvider { get; set; }

[Resolved(CanBeNull = true)]
[CanBeNull]
private IDistanceSnapProvider distanceSnapProvider { get; set; }

[Resolved(CanBeNull = true)]
[CanBeNull]
private FreehandSliderToolboxGroup freehandToolboxGroup { get; set; }

private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder();
private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder { Degree = 4 };

protected override bool IsValidForPlacement => HitObject.Path.HasValidLength;

Expand Down Expand Up @@ -94,6 +98,11 @@ protected override void LoadComplete()
bSplineBuilder.CornerThreshold = e.NewValue;
Scheduler.AddOnce(updateSliderPathFromBSplineBuilder);
}, true);

freehandToolboxGroup.CircleThreshold.BindValueChanged(e =>
{
Scheduler.AddOnce(updateSliderPathFromBSplineBuilder);
}, true);
}
}

Expand Down Expand Up @@ -197,7 +206,14 @@ protected override void OnDragEnd(DragEndEvent e)
base.OnDragEnd(e);

if (state == SliderPlacementState.Drawing)
{
bSplineBuilder.Finish();
updateSliderPathFromBSplineBuilder();

// Change the state so it will snap the expected distance in endCurve.
state = SliderPlacementState.Finishing;
endCurve();
}
}

protected override void OnMouseUp(MouseUpEvent e)
Expand Down Expand Up @@ -232,7 +248,7 @@ private void updatePathType()
{
if (state == SliderPlacementState.Drawing)
{
segmentStart.Type = PathType.BSpline(3);
segmentStart.Type = PathType.BSpline(4);
return;
}

Expand Down Expand Up @@ -300,7 +316,10 @@ private bool canPlaceNewControlPoint([CanBeNull] out PathControlPoint lastPoint)

private void updateSlider()
{
HitObject.Path.ExpectedDistance.Value = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;
if (state == SliderPlacementState.Drawing)
HitObject.Path.ExpectedDistance.Value = (float)HitObject.Path.CalculatedDistance;
else
HitObject.Path.ExpectedDistance.Value = distanceSnapProvider?.FindSnappedDistance(HitObject, (float)HitObject.Path.CalculatedDistance) ?? (float)HitObject.Path.CalculatedDistance;

bodyPiece.UpdateFrom(HitObject);
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
Expand All @@ -309,53 +328,126 @@ private void updateSlider()

private void updateSliderPathFromBSplineBuilder()
{
IReadOnlyList<Vector2> builderPoints = bSplineBuilder.ControlPoints;
IReadOnlyList<List<Vector2>> builderPoints = bSplineBuilder.ControlPoints;

if (builderPoints.Count == 0)
if (builderPoints.Count == 0 || builderPoints[0].Count == 0)
return;

int lastSegmentStart = 0;
PathType? lastPathType = null;

HitObject.Path.ControlPoints.Clear();

// Iterate through generated points, finding each segment and adding non-inheriting path types where appropriate.
// Importantly, the B-Spline builder returns three Vector2s at the same location when a new segment is to be started.
// Iterate through generated segments and adding non-inheriting path types where appropriate.
for (int i = 0; i < builderPoints.Count; i++)
{
bool isLastPoint = i == builderPoints.Count - 1;
bool isNewSegment = i < builderPoints.Count - 2 && builderPoints[i] == builderPoints[i + 1] && builderPoints[i] == builderPoints[i + 2];
bool isLastSegment = i == builderPoints.Count - 1;
var segment = builderPoints[i];

if (segment.Count == 0)
continue;

// Replace this segment with a circular arc if it is a reasonable substitute.
var circleArcSegment = tryCircleArc(segment);

if (circleArcSegment is not null)
{
HitObject.Path.ControlPoints.Add(new PathControlPoint(circleArcSegment[0], PathType.PERFECT_CURVE));
HitObject.Path.ControlPoints.Add(new PathControlPoint(circleArcSegment[1]));
}
else
{
HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[0], PathType.BSpline(4)));
for (int j = 1; j < segment.Count - 1; j++)
HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[j]));
}

if (isLastSegment)
HitObject.Path.ControlPoints.Add(new PathControlPoint(segment[^1]));
}
}

private Vector2[] tryCircleArc(List<Vector2> segment)
{
if (segment.Count < 3 || freehandToolboxGroup?.CircleThreshold.Value == 0) return null;

// Assume the segment creates a reasonable circular arc and then check if it reasonable
var points = PathApproximator.BSplineToPiecewiseLinear(segment.ToArray(), bSplineBuilder.Degree);
var circleArcControlPoints = new[] { points[0], points[points.Count / 2], points[^1] };
var circleArc = new CircularArcProperties(circleArcControlPoints);

if (!circleArc.IsValid) return null;

double length = circleArc.ThetaRange * circleArc.Radius;

if (length > 1000) return null;

double loss = 0;
Vector2? lastPoint = null;
Vector2? lastVec = null;
Vector2? lastVec2 = null;
int? lastDir = null;
int? lastDir2 = null;
double totalWinding = 0;

// Loop through the points and check if they are not too far away from the circular arc.
// Also make sure it curves monotonically in one direction and at most one loop is done.
foreach (var point in points)
{
var vec = point - circleArc.Centre;
loss += Math.Pow((vec.Length - circleArc.Radius) / length, 2);

if (lastVec.HasValue)
{
double det = lastVec.Value.X * vec.Y - lastVec.Value.Y * vec.X;
int dir = Math.Sign(det);

if (isNewSegment || isLastPoint)
if (dir == 0)
continue;

if (lastDir.HasValue && dir != lastDir)
return null; // Circle center is not inside the polygon

lastDir = dir;
}

lastVec = vec;

if (lastPoint.HasValue)
{
int pointsInSegment = i - lastSegmentStart;
var vec2 = point - lastPoint.Value;

// Where possible, we can use the simpler LINEAR path type.
PathType? pathType = pointsInSegment == 1 ? PathType.LINEAR : PathType.BSpline(3);
if (lastVec2.HasValue)
{
double dot = Vector2.Dot(vec2, lastVec2.Value);
double det = lastVec2.Value.X * vec2.Y - lastVec2.Value.Y * vec2.X;
double angle = Math.Atan2(det, dot);
int dir2 = Math.Sign(angle);

// Linear segments can be combined, as two adjacent linear sections are computationally the same as one with the points combined.
if (lastPathType == pathType && lastPathType == PathType.LINEAR)
pathType = null;
if (dir2 == 0)
continue;

HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[lastSegmentStart], pathType));
for (int j = lastSegmentStart + 1; j < i; j++)
HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[j]));
if (lastDir2.HasValue && dir2 != lastDir2)
return null; // Curvature changed, like in an S-shape

if (isLastPoint)
HitObject.Path.ControlPoints.Add(new PathControlPoint(builderPoints[i]));
totalWinding += Math.Abs(angle);
lastDir2 = dir2;
}

// Skip the redundant duplicated points (see isNewSegment above) which have been coalesced into a path type.
lastSegmentStart = (i += 2);
if (pathType != null) lastPathType = pathType;
lastVec2 = vec2;
}

lastPoint = point;
}

loss /= points.Count;

return loss > freehandToolboxGroup?.CircleThreshold.Value || totalWinding > MathHelper.TwoPi ? null : circleArcControlPoints;
}

private enum SliderPlacementState
{
Initial,
ControlPoints,
Drawing
Drawing,
Finishing
}
}
}
42 changes: 37 additions & 5 deletions osu.Game.Rulesets.Osu/Edit/FreehandSliderToolboxGroup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ public FreehandSliderToolboxGroup()
{
}

public BindableFloat Tolerance { get; } = new BindableFloat(1.5f)
public BindableFloat Tolerance { get; } = new BindableFloat(1.8f)
{
MinValue = 0.05f,
MaxValue = 3f,
MaxValue = 2.0f,
Precision = 0.01f
};

Expand All @@ -31,8 +31,15 @@ public FreehandSliderToolboxGroup()
Precision = 0.01f
};

public BindableFloat CircleThreshold { get; } = new BindableFloat(0.0015f)
{
MinValue = 0f,
MaxValue = 0.005f,
Precision = 0.0001f
};

// We map internal ranges to a more standard range of values for display to the user.
private readonly BindableInt displayTolerance = new BindableInt(40)
private readonly BindableInt displayTolerance = new BindableInt(90)
{
MinValue = 5,
MaxValue = 100
Expand All @@ -44,8 +51,15 @@ public FreehandSliderToolboxGroup()
MaxValue = 100
};

private readonly BindableInt displayCircleThreshold = new BindableInt(30)
{
MinValue = 0,
MaxValue = 100
};

private ExpandableSlider<int> toleranceSlider = null!;
private ExpandableSlider<int> cornerThresholdSlider = null!;
private ExpandableSlider<int> circleThresholdSlider = null!;

[BackgroundDependencyLoader]
private void load()
Expand All @@ -59,6 +73,10 @@ private void load()
cornerThresholdSlider = new ExpandableSlider<int>
{
Current = displayCornerThreshold
},
circleThresholdSlider = new ExpandableSlider<int>
{
Current = displayCircleThreshold
}
};
}
Expand All @@ -83,18 +101,32 @@ protected override void LoadComplete()
CornerThreshold.Value = displayToInternalCornerThreshold(threshold.NewValue);
}, true);

displayCircleThreshold.BindValueChanged(threshold =>
{
circleThresholdSlider.ContractedLabelText = $"P. C. T.: {threshold.NewValue:N0}";
circleThresholdSlider.ExpandedLabelText = $"Perfect Curve Threshold: {threshold.NewValue:N0}";

CircleThreshold.Value = displayToInternalCircleThreshold(threshold.NewValue);
}, true);

Tolerance.BindValueChanged(tolerance =>
displayTolerance.Value = internalToDisplayTolerance(tolerance.NewValue)
);
CornerThreshold.BindValueChanged(threshold =>
displayCornerThreshold.Value = internalToDisplayCornerThreshold(threshold.NewValue)
);
CircleThreshold.BindValueChanged(threshold =>
displayCircleThreshold.Value = internalToDisplayCircleThreshold(threshold.NewValue)
);

float displayToInternalTolerance(float v) => v / 33f;
int internalToDisplayTolerance(float v) => (int)Math.Round(v * 33f);
float displayToInternalTolerance(float v) => v / 50f;
int internalToDisplayTolerance(float v) => (int)Math.Round(v * 50f);

float displayToInternalCornerThreshold(float v) => v / 100f;
int internalToDisplayCornerThreshold(float v) => (int)Math.Round(v * 100f);

float displayToInternalCircleThreshold(float v) => v / 20000f;
int internalToDisplayCircleThreshold(float v) => (int)Math.Round(v * 20000f);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public override void ApplyState()
relativeGlowSize = Source.glowSize / Source.DrawSize.X;
}

public override void Draw(IRenderer renderer)
protected override void Draw(IRenderer renderer)
{
base.Draw(renderer);
drawGlow(renderer);
Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ public override void ApplyState()
points.AddRange(Source.SmokePoints.Skip(firstVisiblePointIndex).Take(futurePointIndex - firstVisiblePointIndex));
}

public sealed override void Draw(IRenderer renderer)
protected sealed override void Draw(IRenderer renderer)
{
base.Draw(renderer);

Expand Down
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ public override void ApplyState()

private IUniformBuffer<CursorTrailParameters> cursorTrailParameters;

public override void Draw(IRenderer renderer)
protected override void Draw(IRenderer renderer)
{
base.Draw(renderer);

Expand Down
Loading

0 comments on commit ec6200b

Please sign in to comment.