mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 10:07:52 +08:00
Test for accuracy of perfect curves
This commit is contained in:
parent
b2c4e0e951
commit
d81be56adf
@ -3,8 +3,10 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -72,48 +74,119 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
private void moveMouse(Vector2 pos) =>
|
private void moveMouse(Vector2 pos) =>
|
||||||
AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos)));
|
AddStep($"move mouse to {pos}", () => InputManager.MoveMouseTo(playfield.ToScreenSpace(pos)));
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSliderNearLinearScaling
|
public class TestSliderNearLinearScaling
|
||||||
{
|
{
|
||||||
|
private readonly Random rng = new Random(1337);
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestScalingSliderFlat()
|
public void TestScalingSliderFlat()
|
||||||
{
|
{
|
||||||
Slider sliderPerfect = new Slider
|
SliderPath sliderPathPerfect = new SliderPath(
|
||||||
{
|
[
|
||||||
Position = new Vector2(300),
|
new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE),
|
||||||
Path = new SliderPath(
|
new PathControlPoint(new Vector2(50, 25)),
|
||||||
[
|
new PathControlPoint(new Vector2(25, 100)),
|
||||||
new PathControlPoint(new Vector2(0), PathType.PERFECT_CURVE),
|
]);
|
||||||
new PathControlPoint(new Vector2(50, 25)),
|
|
||||||
new PathControlPoint(new Vector2(25, 100)),
|
|
||||||
])
|
|
||||||
};
|
|
||||||
|
|
||||||
Slider sliderBezier = new Slider
|
SliderPath sliderPathBezier = new SliderPath(
|
||||||
{
|
[
|
||||||
Position = new Vector2(300),
|
new PathControlPoint(new Vector2(0), PathType.BEZIER),
|
||||||
Path = new SliderPath(
|
new PathControlPoint(new Vector2(50, 25)),
|
||||||
[
|
new PathControlPoint(new Vector2(25, 100)),
|
||||||
new PathControlPoint(new Vector2(0), PathType.BEZIER),
|
]);
|
||||||
new PathControlPoint(new Vector2(50, 25)),
|
|
||||||
new PathControlPoint(new Vector2(25, 100)),
|
|
||||||
])
|
|
||||||
};
|
|
||||||
|
|
||||||
scaleSlider(sliderPerfect, new Vector2(0.000001f, 1));
|
scaleSlider(sliderPathPerfect, new Vector2(0.000001f, 1));
|
||||||
scaleSlider(sliderBezier, new Vector2(0.000001f, 1));
|
scaleSlider(sliderPathBezier, new Vector2(0.000001f, 1));
|
||||||
|
|
||||||
for (int i = 0; i < 100; i++)
|
for (int i = 0; i < 100; i++)
|
||||||
{
|
{
|
||||||
Assert.True(Precision.AlmostEquals(sliderPerfect.Path.PositionAt(i / 100.0f), sliderBezier.Path.PositionAt(i / 100.0f)));
|
Assert.True(Precision.AlmostEquals(sliderPathPerfect.PositionAt(i / 100.0f), sliderPathBezier.PositionAt(i / 100.0f)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scaleSlider(Slider slider, Vector2 scale)
|
[Test]
|
||||||
|
public void TestPerfectCurveMatchesTheoretical()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
|
for (int i = 0; i < 20000; i++)
|
||||||
{
|
{
|
||||||
slider.Path.ControlPoints[i].Position *= scale;
|
//Only test points that are in the screen's bounds
|
||||||
|
float p1X = 640.0f * (float)rng.NextDouble();
|
||||||
|
float p2X = 640.0f * (float)rng.NextDouble();
|
||||||
|
|
||||||
|
float p1Y = 480.0f * (float)rng.NextDouble();
|
||||||
|
float p2Y = 480.0f * (float)rng.NextDouble();
|
||||||
|
SliderPath sliderPathPerfect = new SliderPath(
|
||||||
|
[
|
||||||
|
new PathControlPoint(new Vector2(0, 0), PathType.PERFECT_CURVE),
|
||||||
|
new PathControlPoint(new Vector2(p1X, p1Y)),
|
||||||
|
new PathControlPoint(new Vector2(p2X, p2Y)),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assertMatchesPerfectCircle(sliderPathPerfect);
|
||||||
|
|
||||||
|
scaleSlider(sliderPathPerfect, new Vector2(0.00001f, 1));
|
||||||
|
|
||||||
|
assertMatchesPerfectCircle(sliderPathPerfect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMatchesPerfectCircle(SliderPath path)
|
||||||
|
{
|
||||||
|
if (path.ControlPoints.Count != 3)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Replication of PathApproximator.CircularArcToPiecewiseLinear
|
||||||
|
CircularArcProperties circularArcProperties = new CircularArcProperties(path.ControlPoints.Select(x => x.Position).ToArray());
|
||||||
|
|
||||||
|
if (!circularArcProperties.IsValid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
//Addresses cases where circularArcProperties.ThetaRange>0.5
|
||||||
|
//Occurs in code in PathControlPointVisualiser.ensureValidPathType
|
||||||
|
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(path.ControlPoints.Select(x => x.Position).ToArray());
|
||||||
|
if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int subpoints = (2f * circularArcProperties.Radius <= 0.1f) ? 2 : Math.Max(2, (int)Math.Ceiling(circularArcProperties.ThetaRange / (2.0 * Math.Acos(1f - 0.1f / circularArcProperties.Radius))));
|
||||||
|
|
||||||
|
//ignore cases where subpoints is int.MaxValue, result will be garbage
|
||||||
|
//as well, having this many subpoints will cause an out of memory error, so can't happen during normal useage
|
||||||
|
if (subpoints == int.MaxValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int i = 0; i < Math.Min(subpoints, 100); i++)
|
||||||
|
{
|
||||||
|
float progress = (float)rng.NextDouble();
|
||||||
|
|
||||||
|
//To avoid errors from interpolating points, ensure we check only positions that would be subpoints.
|
||||||
|
progress = (float)Math.Ceiling(progress * (subpoints - 1)) / (subpoints - 1);
|
||||||
|
|
||||||
|
//Special case - if few subpoints, ensure checking every single one rather than randomly
|
||||||
|
if (subpoints < 100)
|
||||||
|
progress = i / (float)(subpoints - 1);
|
||||||
|
|
||||||
|
//edge points cause issue with interpolation, so ignore the last two points and first
|
||||||
|
if (progress == 0.0f || progress >= (subpoints - 2) / (float)(subpoints - 1))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
double theta = circularArcProperties.ThetaStart + circularArcProperties.Direction * progress * circularArcProperties.ThetaRange;
|
||||||
|
Vector2 vector = new Vector2((float)Math.Cos(theta), (float)Math.Sin(theta)) * circularArcProperties.Radius;
|
||||||
|
|
||||||
|
Assert.True(Precision.AlmostEquals(circularArcProperties.Centre + vector, path.PositionAt(progress), 0.01f),
|
||||||
|
"A perfect circle with points " + string.Join(", ", path.ControlPoints.Select(x => x.Position)) + " and radius" + circularArcProperties.Radius + "from SliderPath does not almost equal a theoretical perfect circle with " + subpoints + " subpoints"
|
||||||
|
+ ": " + (circularArcProperties.Centre + vector) + " - " + path.PositionAt(progress)
|
||||||
|
+ " = " + (circularArcProperties.Centre + vector - path.PositionAt(progress))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scaleSlider(SliderPath path, Vector2 scale)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < path.ControlPoints.Count; i++)
|
||||||
|
{
|
||||||
|
path.ControlPoints[i].Position *= scale;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user