Skip to content

Commit

Permalink
Add class for efficiently building B-Splines
Browse files Browse the repository at this point in the history
This adds the IncrementalBSplineBuilder class which can be used to
build up a B-Spline from a series of linear points efficiently.
The class takes special care to keep the number of control points
  • Loading branch information
PM-CS committed Nov 11, 2023
1 parent 0bf4656 commit 6d579af
Show file tree
Hide file tree
Showing 9 changed files with 359 additions and 50 deletions.
4 changes: 2 additions & 2 deletions osu.Framework.Tests/MathUtils/TestPathApproximator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public void TestLagrange()
// lagrange of (0,0) (0.5,0.35) (1,1) is equal to 0.6x*x + 0.4x
Vector2[] points = { new Vector2(0, 0), new Vector2(0.5f, 0.35f), new Vector2(1, 1) };

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

for (int i = 0; i < approximated.Count; i++)
Expand All @@ -34,7 +34,7 @@ public void TestBSpline()
{
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) };

List<Vector2> approximated = PathApproximator.ApproximateBSpline(points, 4);
List<Vector2> approximated = PathApproximator.BSplineToPiecewiseLinear(points, 4);
Assert.AreEqual(approximated.Count, 29, "Approximated path should have 29 points to test");
Assert.True(Precision.AlmostEquals(approximated[0], points[0], 1e-4f));
Assert.True(Precision.AlmostEquals(approximated[28], points[6], 1e-4f));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ protected override void LoadComplete()
if (copy.Length != 3)
return;
path.Vertices = PathApproximator.ApproximateCircularArc(copy);
path.Vertices = PathApproximator.CircularArcToPiecewiseLinear(copy);
var bounds = PathApproximator.CircularArcBoundingBox(copy);
boundingBox.Size = bounds.Size;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ protected override void Update()
vectorPath.AddRange(ordered.Select(p => p.PointPosition.Value));
vectorPath.Add(new Vector2(DrawWidth, 0));

var bezierPath = PathApproximator.ApproximateBezier(vectorPath.ToArray());
var bezierPath = PathApproximator.BezierToPiecewiseLinear(vectorPath.ToArray());
path.Vertices = bezierPath;
path.Position = -path.PositionInBoundingBox(Vector2.Zero);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// 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.

using osu.Framework.Graphics;
using osuTK.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Lines;
using osu.Framework.Input.Events;
using osuTK.Input;
using osu.Framework.Utils;
using osuTK;

namespace osu.Framework.Tests.Visual.Drawables
{
[System.ComponentModel.Description("Approximate a hand-drawn path with minimal B-spline control points")]
public partial class TestSceneInteractivePathDrawing : FrameworkTestScene
{
private readonly Path rawDrawnPath;
private readonly Path approximatedDrawnPath;
private readonly Container controlPointViz;

private readonly IncrementalBSplineBuilder bSplineBuilder = new IncrementalBSplineBuilder();

public TestSceneInteractivePathDrawing()
{
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
rawDrawnPath = new Path
{
Colour = Color4.DeepPink,
PathRadius = 5,
},
approximatedDrawnPath = new Path
{
Colour = Color4.Blue,
PathRadius = 3,
},
controlPointViz = new Container
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.5f,
},
}
};

updateViz();
OnUpdate += _ => updateControlPointsViz();

AddStep("Reset path", () =>
{
bSplineBuilder.Clear();
});

AddSliderStep($"{nameof(bSplineBuilder.Degree)}", 1, 5, 3, v =>
{
bSplineBuilder.Degree = v;
});
AddSliderStep($"{nameof(bSplineBuilder.Tolerance)}", 0f, 1f, 0.1f, v =>
{
bSplineBuilder.Tolerance = v;
});
}

private void updateControlPointsViz()
{
controlPointViz.Clear();

foreach (var cp in bSplineBuilder.GetControlPoints())
{
controlPointViz.Add(new Box
{
Origin = Anchor.Centre,
Size = new Vector2(10),
Position = cp,
Colour = Color4.LightGreen,
});
}
}

protected override bool OnDragStart(DragStartEvent e)
{
if (e.Button == MouseButton.Left)
{
bSplineBuilder.Clear();
bSplineBuilder.AddLinearPoint(rawDrawnPath.ToLocalSpace(ToScreenSpace(e.MousePosition)));
return true;
}

return false;
}

private void updateViz()
{
rawDrawnPath.Vertices = bSplineBuilder.GetInputPath();
approximatedDrawnPath.Vertices = bSplineBuilder.OutputPath;

updateControlPointsViz();
}

protected override void OnDrag(DragEvent e)
{
bSplineBuilder.AddLinearPoint(rawDrawnPath.ToLocalSpace(ToScreenSpace(e.MousePosition)));
}
}
}
28 changes: 14 additions & 14 deletions osu.Framework.Tests/Visual/Drawables/TestScenePathApproximator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,30 @@ namespace osu.Framework.Tests.Visual.Drawables
public partial class TestScenePathApproximator : GridTestScene
{
public TestScenePathApproximator()
: base(2, 2)
: base(2, 3)
{
Cell(0).AddRange(new[]
{
createLabel("ApproximateBezier"),
new ApproximatedPathTest(PathApproximator.ApproximateBezier),
createLabel("BezierToPiecewiseLinear"),
new ApproximatedPathTest(PathApproximator.BezierToPiecewiseLinear),
});

Cell(1).AddRange(new[]
Cell(2).AddRange(new[]
{
createLabel("ApproximateCatmull"),
new ApproximatedPathTest(PathApproximator.ApproximateCatmull),
createLabel("CatmullToPiecewiseLinear"),
new ApproximatedPathTest(PathApproximator.CatmullToPiecewiseLinear),
});

Cell(2).AddRange(new[]
Cell(3).AddRange(new[]
{
createLabel("ApproximateCircularArc"),
new ApproximatedPathTest(PathApproximator.ApproximateCircularArc),
createLabel("CircularArcToPiecewiseLinear"),
new ApproximatedPathTest(PathApproximator.CircularArcToPiecewiseLinear),
});

Cell(3).AddRange(new[]
Cell(4).AddRange(new[]
{
createLabel("ApproximateLagrangePolynomial"),
new ApproximatedPathTest(PathApproximator.ApproximateLagrangePolynomial),
createLabel("LagrangePolynomialToPiecewiseLinear"),
new ApproximatedPathTest(PathApproximator.LagrangePolynomialToPiecewiseLinear),
});
}

Expand All @@ -50,10 +50,10 @@ public TestScenePathApproximator()
Colour = Color4.White,
};

public delegate List<Vector2> ApproximatorFunc(ReadOnlySpan<Vector2> controlPoints);

private partial class ApproximatedPathTest : SmoothPath
{
public delegate List<Vector2> ApproximatorFunc(ReadOnlySpan<Vector2> controlPoints);

public ApproximatedPathTest(ApproximatorFunc approximator)
{
Vector2[] points = new Vector2[5];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public override void Draw(IRenderer renderer)
shader.Bind();
shader.BindUniformBlock("g_ColourBuffer", colourBuffer);

// Submit vertices, making sure that we don't submit an index which would overflow the SSBO.
// Submit rawDrawnPath, making sure that we don't submit an index which would overflow the SSBO.
for (int i = 0; i < areas.Count; i++)
{
vertices.Add(new ColourIndexedVertex
Expand Down
10 changes: 10 additions & 0 deletions osu.Framework/Graphics/Lines/Path.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,16 @@ public void AddVertex(Vector2 pos)
Invalidate(Invalidation.DrawSize);
}

public void ReplaceVertex(int index, Vector2 pos)
{
vertices[index] = pos;

vertexBoundsCache.Invalidate();
segmentsCache.Invalidate();

Invalidate(Invalidation.DrawSize);
}

private readonly List<Line> segmentsBacking = new List<Line>();
private readonly Cached segmentsCache = new Cached();
private List<Line> segments => segmentsCache.IsValid ? segmentsBacking : generateSegments();
Expand Down
Loading

0 comments on commit 6d579af

Please sign in to comment.