Skip to content

Commit 1962cf6

Browse files
authored
Merge pull request #6044 from default0/path-approximation
Add class for efficiently building B-Splines
2 parents 5c5c6c5 + aefd351 commit 1962cf6

8 files changed

+357
-45
lines changed

osu.Framework.Tests/MathUtils/TestPathApproximator.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public void TestLagrange()
1717
// lagrange of (0,0) (0.5,0.35) (1,1) is equal to 0.6x*x + 0.4x
1818
Vector2[] points = { new Vector2(0, 0), new Vector2(0.5f, 0.35f), new Vector2(1, 1) };
1919

20-
List<Vector2> approximated = PathApproximator.ApproximateLagrangePolynomial(points);
20+
List<Vector2> approximated = PathApproximator.LagrangePolynomialToPiecewiseLinear(points);
2121
Assert.Greater(approximated.Count, 10, "Approximated polynomial should have at least 10 points to test");
2222

2323
for (int i = 0; i < approximated.Count; i++)
@@ -34,7 +34,7 @@ public void TestBSpline()
3434
{
3535
Vector2[] points = { new Vector2(0, 0), new Vector2(1, 0), new Vector2(1, -1), new Vector2(-1, -1), new Vector2(-1, 1), new Vector2(3, 2), new Vector2(3, 0) };
3636

37-
List<Vector2> approximated = PathApproximator.ApproximateBSpline(points, 4);
37+
List<Vector2> approximated = PathApproximator.BSplineToPiecewiseLinear(points, 4);
3838
Assert.AreEqual(approximated.Count, 29, "Approximated path should have 29 points to test");
3939
Assert.True(Precision.AlmostEquals(approximated[0], points[0], 1e-4f));
4040
Assert.True(Precision.AlmostEquals(approximated[28], points[6], 1e-4f));

osu.Framework.Tests/Visual/Drawables/TestSceneCircularArcBoundingBox.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ protected override void LoadComplete()
7474
if (copy.Length != 3)
7575
return;
7676

77-
path.Vertices = PathApproximator.ApproximateCircularArc(copy);
77+
path.Vertices = PathApproximator.CircularArcToPiecewiseLinear(copy);
7878

7979
var bounds = PathApproximator.CircularArcBoundingBox(copy);
8080
boundingBox.Size = bounds.Size;

osu.Framework.Tests/Visual/Drawables/TestSceneCustomEasingCurve.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ protected override void Update()
165165
vectorPath.AddRange(ordered.Select(p => p.PointPosition.Value));
166166
vectorPath.Add(new Vector2(DrawWidth, 0));
167167

168-
var bezierPath = PathApproximator.ApproximateBezier(vectorPath.ToArray());
168+
var bezierPath = PathApproximator.BezierToPiecewiseLinear(vectorPath.ToArray());
169169
path.Vertices = bezierPath;
170170
path.Position = -path.PositionInBoundingBox(Vector2.Zero);
171171

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
2+
// See the LICENCE file in the repository root for full licence text.
3+
4+
using osu.Framework.Graphics;
5+
using osuTK.Graphics;
6+
using osu.Framework.Graphics.Containers;
7+
using osu.Framework.Graphics.Shapes;
8+
using osu.Framework.Graphics.Lines;
9+
using osu.Framework.Input.Events;
10+
using osuTK.Input;
11+
using osu.Framework.Utils;
12+
using osuTK;
13+
14+
namespace osu.Framework.Tests.Visual.Drawables
15+
{
16+
[System.ComponentModel.Description("Approximate a hand-drawn path with minimal B-spline control points")]
17+
public partial class TestSceneInteractivePathDrawing : FrameworkTestScene
18+
{
19+
private readonly Path rawDrawnPath;
20+
private readonly Path approximatedDrawnPath;
21+
private readonly Container controlPointViz;
22+
23+
private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder();
24+
25+
public TestSceneInteractivePathDrawing()
26+
{
27+
Child = new Container
28+
{
29+
RelativeSizeAxes = Axes.Both,
30+
Children = new Drawable[]
31+
{
32+
rawDrawnPath = new Path
33+
{
34+
Colour = Color4.DeepPink,
35+
PathRadius = 5,
36+
},
37+
approximatedDrawnPath = new Path
38+
{
39+
Colour = Color4.Blue,
40+
PathRadius = 3,
41+
},
42+
controlPointViz = new Container
43+
{
44+
RelativeSizeAxes = Axes.Both,
45+
Alpha = 0.5f,
46+
},
47+
}
48+
};
49+
50+
updateViz();
51+
OnUpdate += _ => updateViz();
52+
53+
AddStep("Reset path", () =>
54+
{
55+
bSplineBuilder.Clear();
56+
});
57+
58+
AddSliderStep($"{nameof(bSplineBuilder.Degree)}", 1, 5, 3, v =>
59+
{
60+
bSplineBuilder.Degree = v;
61+
});
62+
AddSliderStep($"{nameof(bSplineBuilder.Tolerance)}", 0f, 1f, 0.1f, v =>
63+
{
64+
bSplineBuilder.Tolerance = v;
65+
});
66+
}
67+
68+
private void updateControlPointsViz()
69+
{
70+
controlPointViz.Clear();
71+
72+
foreach (var cp in bSplineBuilder.GetControlPoints())
73+
{
74+
controlPointViz.Add(new Box
75+
{
76+
Origin = Anchor.Centre,
77+
Size = new Vector2(10),
78+
Position = cp,
79+
Colour = Color4.LightGreen,
80+
});
81+
}
82+
}
83+
84+
protected override bool OnDragStart(DragStartEvent e)
85+
{
86+
if (e.Button == MouseButton.Left)
87+
{
88+
bSplineBuilder.Clear();
89+
bSplineBuilder.AddLinearPoint(rawDrawnPath.ToLocalSpace(ToScreenSpace(e.MousePosition)));
90+
return true;
91+
}
92+
93+
return false;
94+
}
95+
96+
private void updateViz()
97+
{
98+
rawDrawnPath.Vertices = bSplineBuilder.GetInputPath();
99+
approximatedDrawnPath.Vertices = bSplineBuilder.OutputPath;
100+
101+
updateControlPointsViz();
102+
}
103+
104+
protected override void OnDrag(DragEvent e)
105+
{
106+
bSplineBuilder.AddLinearPoint(rawDrawnPath.ToLocalSpace(ToScreenSpace(e.MousePosition)));
107+
}
108+
}
109+
}

osu.Framework.Tests/Visual/Drawables/TestScenePathApproximator.cs

+10-10
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,26 @@ public TestScenePathApproximator()
2020
{
2121
Cell(0).AddRange(new[]
2222
{
23-
createLabel("ApproximateBezier"),
24-
new ApproximatedPathTest(PathApproximator.ApproximateBezier),
23+
createLabel(nameof(PathApproximator.BezierToPiecewiseLinear)),
24+
new ApproximatedPathTest(PathApproximator.BezierToPiecewiseLinear),
2525
});
2626

2727
Cell(1).AddRange(new[]
2828
{
29-
createLabel("ApproximateCatmull"),
30-
new ApproximatedPathTest(PathApproximator.ApproximateCatmull),
29+
createLabel(nameof(PathApproximator.CatmullToPiecewiseLinear)),
30+
new ApproximatedPathTest(PathApproximator.CatmullToPiecewiseLinear),
3131
});
3232

3333
Cell(2).AddRange(new[]
3434
{
35-
createLabel("ApproximateCircularArc"),
36-
new ApproximatedPathTest(PathApproximator.ApproximateCircularArc),
35+
createLabel(nameof(PathApproximator.CircularArcToPiecewiseLinear)),
36+
new ApproximatedPathTest(PathApproximator.CircularArcToPiecewiseLinear),
3737
});
3838

3939
Cell(3).AddRange(new[]
4040
{
41-
createLabel("ApproximateLagrangePolynomial"),
42-
new ApproximatedPathTest(PathApproximator.ApproximateLagrangePolynomial),
41+
createLabel(nameof(PathApproximator.LagrangePolynomialToPiecewiseLinear)),
42+
new ApproximatedPathTest(PathApproximator.LagrangePolynomialToPiecewiseLinear),
4343
});
4444
}
4545

@@ -50,10 +50,10 @@ public TestScenePathApproximator()
5050
Colour = Color4.White,
5151
};
5252

53+
public delegate List<Vector2> ApproximatorFunc(ReadOnlySpan<Vector2> controlPoints);
54+
5355
private partial class ApproximatedPathTest : SmoothPath
5456
{
55-
public delegate List<Vector2> ApproximatorFunc(ReadOnlySpan<Vector2> controlPoints);
56-
5757
public ApproximatedPathTest(ApproximatorFunc approximator)
5858
{
5959
Vector2[] points = new Vector2[5];

osu.Framework/Graphics/Lines/Path.cs

+10
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,16 @@ public void AddVertex(Vector2 pos)
237237
Invalidate(Invalidation.DrawSize);
238238
}
239239

240+
public void ReplaceVertex(int index, Vector2 pos)
241+
{
242+
vertices[index] = pos;
243+
244+
vertexBoundsCache.Invalidate();
245+
segmentsCache.Invalidate();
246+
247+
Invalidate(Invalidation.DrawSize);
248+
}
249+
240250
private readonly List<Line> segmentsBacking = new List<Line>();
241251
private readonly Cached segmentsCache = new Cached();
242252
private List<Line> segments => segmentsCache.IsValid ? segmentsBacking : generateSegments();

0 commit comments

Comments
 (0)