diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementMask.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementMask.cs index ad75779d80..889ea0c311 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementMask.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementMask.cs @@ -4,7 +4,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Edit.Masks.Slider; +using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/Slider/Components/SliderControlPoint.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderControlPoint.cs similarity index 97% rename from osu.Game.Rulesets.Osu/Edit/Masks/Slider/Components/SliderControlPoint.cs rename to osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderControlPoint.cs index 47bae20c30..7d2f94a82d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Masks/Slider/Components/SliderControlPoint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderControlPoint.cs @@ -10,7 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using OpenTK; -namespace osu.Game.Rulesets.Osu.Edit.Masks.Slider.Components +namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components { /// /// Todo: Move this out of SliderPlacementMask... diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/Slider/SliderPlacementMask.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderPlacementMask.cs similarity index 79% rename from osu.Game.Rulesets.Osu/Edit/Masks/Slider/SliderPlacementMask.cs rename to osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderPlacementMask.cs index 94ac8208f4..2ee996fb00 100644 --- a/osu.Game.Rulesets.Osu/Edit/Masks/Slider/SliderPlacementMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderPlacementMask.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -12,11 +13,11 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Osu.Edit.Masks.Slider.Components; +using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components; using OpenTK; using OpenTK.Input; -namespace osu.Game.Rulesets.Osu.Edit.Masks.Slider +namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks { public class SliderPlacementMask : PlacementMask { @@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.Slider { InternalChildren = new Drawable[] { - new BodyPiece(HitObject), + new SliderBodyPiece(HitObject), new SliderCirclePiece(HitObject, SliderPosition.Start), new SliderCirclePiece(HitObject, SliderPosition.End), controlPointContainer = new Container { RelativeSizeAxes = Axes.Both } @@ -113,8 +114,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.Slider private void endCurve() { - HitObject.ControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToList(); - HitObject.CurveType = HitObject.ControlPoints.Count > 2 ? CurveType.Bezier : CurveType.Linear; + HitObject.ControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray(); + HitObject.CurveType = HitObject.ControlPoints.Length > 2 ? CurveType.Bezier : CurveType.Linear; HitObject.Distance = segments.Sum(s => s.Distance); EndPlacement(); @@ -127,8 +128,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.Slider for (int i = 0; i < segments.Count; i++) segments[i].Calculate(i == segments.Count - 1 ? (Vector2?)cursor : null); - HitObject.ControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToList(); - HitObject.CurveType = HitObject.ControlPoints.Count > 2 ? CurveType.Bezier : CurveType.Linear; + HitObject.ControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray(); + HitObject.CurveType = HitObject.ControlPoints.Length > 2 ? CurveType.Bezier : CurveType.Linear; HitObject.Distance = segments.Sum(s => s.Distance); } @@ -154,32 +155,31 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.Slider ControlPoints.Add(offset); } - public List Calculate(Vector2? cursor = null) + public void Calculate(Vector2? cursor = null) { - var allControlPoints = ControlPoints.ToList(); + Span allControlPoints = stackalloc Vector2[ControlPoints.Count + (cursor.HasValue ? 1 : 0)]; + + for (int i = 0; i < ControlPoints.Count; i++) + allControlPoints[i] = ControlPoints[i]; if (cursor.HasValue) - allControlPoints.Add(cursor.Value); + allControlPoints[allControlPoints.Length - 1] = cursor.Value; - IApproximator approximator; + List result; - switch (allControlPoints.Count) + switch (allControlPoints.Length) { case 1: case 2: - approximator = new LinearApproximator(); + result = new LinearApproximator(allControlPoints).CreateLinear(); break; default: - approximator = new BezierApproximator(); + result = new BezierApproximator(allControlPoints).CreateBezier(); break; } Distance = 0; - - var points = approximator.Approximate(allControlPoints); - for (int i = 0; i < points.Count - 1; i++) - Distance += Vector2.Distance(points[i], points[i + 1]); - - return points; + for (int i = 0; i < result.Count - 1; i++) + Distance += Vector2.Distance(result[i], result[i + 1]); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs index 30ebc51179..fd0430ce4c 100644 --- a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs @@ -3,7 +3,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; -using osu.Game.Rulesets.Osu.Edit.Masks.Slider; +using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks; using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Edit diff --git a/osu.Game/Rulesets/Objects/BezierApproximator.cs b/osu.Game/Rulesets/Objects/BezierApproximator.cs index 011526339e..a1803e32f7 100644 --- a/osu.Game/Rulesets/Objects/BezierApproximator.cs +++ b/osu.Game/Rulesets/Objects/BezierApproximator.cs @@ -1,77 +1,29 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using OpenTK; namespace osu.Game.Rulesets.Objects { - public class BezierApproximator : IApproximator + public readonly ref struct BezierApproximator { + private readonly int count; + private readonly ReadOnlySpan controlPoints; + private readonly Vector2[] subdivisionBuffer1; + private readonly Vector2[] subdivisionBuffer2; + private const float tolerance = 0.25f; private const float tolerance_sq = tolerance * tolerance; - private int count; - private Vector2[] subdivisionBuffer1; - private Vector2[] subdivisionBuffer2; - - /// - /// Creates a piecewise-linear approximation of a bezier curve, by adaptively repeatedly subdividing - /// the control points until their approximation error vanishes below a given threshold. - /// - /// A list of vectors representing the piecewise-linear approximation. - public List Approximate(List controlPoints) + public BezierApproximator(ReadOnlySpan controlPoints) { - count = controlPoints.Count; + this.controlPoints = controlPoints; + count = controlPoints.Length; + subdivisionBuffer1 = new Vector2[count]; subdivisionBuffer2 = new Vector2[count * 2 - 1]; - - List output = new List(); - - if (count == 0) - return output; - - Stack toFlatten = new Stack(); - Stack freeBuffers = new Stack(); - - // "toFlatten" contains all the curves which are not yet approximated well enough. - // We use a stack to emulate recursion without the risk of running into a stack overflow. - // (More specifically, we iteratively and adaptively refine our curve with a - // Depth-first search - // over the tree resulting from the subdivisions we make.) - toFlatten.Push(controlPoints.ToArray()); - - Vector2[] leftChild = subdivisionBuffer2; - - while (toFlatten.Count > 0) - { - Vector2[] parent = toFlatten.Pop(); - if (isFlatEnough(parent)) - { - // If the control points we currently operate on are sufficiently "flat", we use - // an extension to De Casteljau's algorithm to obtain a piecewise-linear approximation - // of the bezier curve represented by our control points, consisting of the same amount - // of points as there are control points. - approximate(parent, output); - freeBuffers.Push(parent); - continue; - } - - // If we do not yet have a sufficiently "flat" (in other words, detailed) approximation we keep - // subdividing the curve we are currently operating on. - Vector2[] rightChild = freeBuffers.Count > 0 ? freeBuffers.Pop() : new Vector2[count]; - subdivide(parent, leftChild, rightChild); - - // We re-use the buffer of the parent for one of the children, so that we save one allocation per iteration. - for (int i = 0; i < count; ++i) - parent[i] = leftChild[i]; - - toFlatten.Push(rightChild); - toFlatten.Push(parent); - } - - output.Add(controlPoints[count - 1]); - return output; } /// @@ -140,5 +92,60 @@ namespace osu.Game.Rulesets.Objects output.Add(p); } } + + /// + /// Creates a piecewise-linear approximation of a bezier curve, by adaptively repeatedly subdividing + /// the control points until their approximation error vanishes below a given threshold. + /// + /// A list of vectors representing the piecewise-linear approximation. + public List CreateBezier() + { + List output = new List(); + + if (count == 0) + return output; + + Stack toFlatten = new Stack(); + Stack freeBuffers = new Stack(); + + // "toFlatten" contains all the curves which are not yet approximated well enough. + // We use a stack to emulate recursion without the risk of running into a stack overflow. + // (More specifically, we iteratively and adaptively refine our curve with a + // Depth-first search + // over the tree resulting from the subdivisions we make.) + toFlatten.Push(controlPoints.ToArray()); + + Vector2[] leftChild = subdivisionBuffer2; + + while (toFlatten.Count > 0) + { + Vector2[] parent = toFlatten.Pop(); + if (isFlatEnough(parent)) + { + // If the control points we currently operate on are sufficiently "flat", we use + // an extension to De Casteljau's algorithm to obtain a piecewise-linear approximation + // of the bezier curve represented by our control points, consisting of the same amount + // of points as there are control points. + approximate(parent, output); + freeBuffers.Push(parent); + continue; + } + + // If we do not yet have a sufficiently "flat" (in other words, detailed) approximation we keep + // subdividing the curve we are currently operating on. + Vector2[] rightChild = freeBuffers.Count > 0 ? freeBuffers.Pop() : new Vector2[count]; + subdivide(parent, leftChild, rightChild); + + // We re-use the buffer of the parent for one of the children, so that we save one allocation per iteration. + for (int i = 0; i < count; ++i) + parent[i] = leftChild[i]; + + toFlatten.Push(rightChild); + toFlatten.Push(parent); + } + + output.Add(controlPoints[count - 1]); + return output; + } } } diff --git a/osu.Game/Rulesets/Objects/CatmullApproximator.cs b/osu.Game/Rulesets/Objects/CatmullApproximator.cs index 624f5fc9ab..78f8e471f3 100644 --- a/osu.Game/Rulesets/Objects/CatmullApproximator.cs +++ b/osu.Game/Rulesets/Objects/CatmullApproximator.cs @@ -1,32 +1,40 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using OpenTK; namespace osu.Game.Rulesets.Objects { - public class CatmullApproximator : IApproximator + public readonly ref struct CatmullApproximator { /// /// The amount of pieces to calculate for each controlpoint quadruplet. /// private const int detail = 50; + private readonly ReadOnlySpan controlPoints; + + public CatmullApproximator(ReadOnlySpan controlPoints) + { + this.controlPoints = controlPoints; + } + /// /// Creates a piecewise-linear approximation of a Catmull-Rom spline. /// /// A list of vectors representing the piecewise-linear approximation. - public List Approximate(List controlPoints) + public List CreateCatmull() { - var result = new List(); + var result = new List((controlPoints.Length - 1) * detail * 2); - for (int i = 0; i < controlPoints.Count - 1; i++) + for (int i = 0; i < controlPoints.Length - 1; i++) { var v1 = i > 0 ? controlPoints[i - 1] : controlPoints[i]; var v2 = controlPoints[i]; - var v3 = i < controlPoints.Count - 1 ? controlPoints[i + 1] : v2 + v2 - v1; - var v4 = i < controlPoints.Count - 2 ? controlPoints[i + 2] : v3 + v3 - v2; + var v3 = i < controlPoints.Length - 1 ? controlPoints[i + 1] : v2 + v2 - v1; + var v4 = i < controlPoints.Length - 2 ? controlPoints[i + 2] : v3 + v3 - v2; for (int c = 0; c < detail; c++) { diff --git a/osu.Game/Rulesets/Objects/CircularArcApproximator.cs b/osu.Game/Rulesets/Objects/CircularArcApproximator.cs index 201c6296ba..28d7442aaf 100644 --- a/osu.Game/Rulesets/Objects/CircularArcApproximator.cs +++ b/osu.Game/Rulesets/Objects/CircularArcApproximator.cs @@ -8,19 +8,23 @@ using OpenTK; namespace osu.Game.Rulesets.Objects { - public class CircularArcApproximator : IApproximator + public readonly ref struct CircularArcApproximator { private const float tolerance = 0.1f; + private readonly ReadOnlySpan controlPoints; + + public CircularArcApproximator(ReadOnlySpan controlPoints) + { + this.controlPoints = controlPoints; + } + /// /// Creates a piecewise-linear approximation of a circular arc curve. /// /// A list of vectors representing the piecewise-linear approximation. - public List Approximate(List controlPoints) + public List CreateArc() { - if (controlPoints.Count != 3) - throw new ArgumentException("Must have 3 control points to perform circular arc approximation.", nameof(controlPoints)); - Vector2 a = controlPoints[0]; Vector2 b = controlPoints[1]; Vector2 c = controlPoints[2]; diff --git a/osu.Game/Rulesets/Objects/IApproximator.cs b/osu.Game/Rulesets/Objects/IApproximator.cs deleted file mode 100644 index f865172bb4..0000000000 --- a/osu.Game/Rulesets/Objects/IApproximator.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; -using OpenTK; - -namespace osu.Game.Rulesets.Objects -{ - public interface IApproximator - { - List Approximate(List controlPoints); - } -} diff --git a/osu.Game/Rulesets/Objects/LinearApproximator.cs b/osu.Game/Rulesets/Objects/LinearApproximator.cs index a2c2dd5a93..c513d40ad6 100644 --- a/osu.Game/Rulesets/Objects/LinearApproximator.cs +++ b/osu.Game/Rulesets/Objects/LinearApproximator.cs @@ -1,13 +1,29 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using OpenTK; namespace osu.Game.Rulesets.Objects { - public class LinearApproximator : IApproximator + public readonly ref struct LinearApproximator { - public List Approximate(List controlpoints) => controlpoints; + private readonly ReadOnlySpan controlPoints; + + public LinearApproximator(ReadOnlySpan controlPoints) + { + this.controlPoints = controlPoints; + } + + public List CreateLinear() + { + var result = new List(controlPoints.Length); + + foreach (var c in controlPoints) + result.Add(c); + + return result; + } } } diff --git a/osu.Game/Rulesets/Objects/SliderCurve.cs b/osu.Game/Rulesets/Objects/SliderCurve.cs index 124195b033..e3c9c53a2b 100644 --- a/osu.Game/Rulesets/Objects/SliderCurve.cs +++ b/osu.Game/Rulesets/Objects/SliderCurve.cs @@ -14,10 +14,12 @@ namespace osu.Game.Rulesets.Objects { public double Distance; - public Vector2[] ControlPoints; + public Vector2[] ControlPoints = Array.Empty(); public CurveType CurveType = CurveType.PerfectCurve; + public Vector2 Offset; + private readonly List calculatedPath = new List(); private readonly List cumulativeLength = new List(); @@ -26,11 +28,7 @@ namespace osu.Game.Rulesets.Objects switch (CurveType) { case CurveType.Linear: - var result = new List(subControlPoints.Length); - foreach (var c in subControlPoints) - result.Add(c); - - return result; + return new LinearApproximator(subControlPoints).CreateLinear(); case CurveType.PerfectCurve: //we can only use CircularArc iff we have exactly three control points and no dissection. if (ControlPoints.Length != 3 || subControlPoints.Length != 3) @@ -185,12 +183,12 @@ namespace osu.Game.Rulesets.Objects int i = 0; for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i) { } - path.Add(interpolateVertices(i, d0)); + path.Add(interpolateVertices(i, d0) + Offset); for (; i < calculatedPath.Count && cumulativeLength[i] <= d1; ++i) - path.Add(calculatedPath[i]); + path.Add(calculatedPath[i] + Offset); - path.Add(interpolateVertices(i, d1)); + path.Add(interpolateVertices(i, d1) + Offset); } /// @@ -205,7 +203,7 @@ namespace osu.Game.Rulesets.Objects Calculate(); double d = progressToDistance(progress); - return interpolateVertices(indexOfDistance(d), d); + return interpolateVertices(indexOfDistance(d), d) + Offset; } } }