// Copyright (c) 2007-2016 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE using System.Collections.Generic; using OpenTK; using System.Linq; using System.Diagnostics; using osu.Framework.MathUtils; using System; namespace osu.Game.Modes.Osu.Objects { public class SliderCurve { public double Length; public List Path; public CurveTypes CurveType; private List calculatedPath = new List(); private List cumulativeLength = new List(); private List calculateSubpath(List subpath) { switch (CurveType) { case CurveTypes.Linear: return subpath; default: return new BezierApproximator(subpath).CreateBezier(); } } private void calculatePath() { calculatedPath.Clear(); // Sliders may consist of various subpaths separated by two consecutive vertices // with the same position. The following loop parses these subpaths and computes // their shape independently, consecutively appending them to calculatedPath. List subpath = new List(); for (int i = 0; i < Path.Count; ++i) { subpath.Add(Path[i]); if (i == Path.Count - 1 || Path[i] == Path[i + 1]) { // If we already constructed a subpath previously, then the new subpath // will have as starting position the end position of the previous subpath. // Hence we can and should remove the previous endpoint to avoid a segment // with 0 length. if (calculatedPath.Count > 0) calculatedPath.RemoveAt(calculatedPath.Count - 1); calculatedPath.AddRange(calculateSubpath(subpath)); subpath.Clear(); } } } private void calculateCumulativeLengthAndTrimPath() { double l = 0; cumulativeLength.Clear(); cumulativeLength.Add(l); for (int i = 0; i < calculatedPath.Count - 1; ++i) { Vector2 diff = calculatedPath[i + 1] - calculatedPath[i]; double d = diff.Length; // Shorten slider curves that are too long compared to what's // in the .osu file. if (Length - l < d) { calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((Length - l) / d); calculatedPath.RemoveRange(i + 2, calculatedPath.Count - 2 - i); l = Length; cumulativeLength.Add(l); break; } l += d; cumulativeLength.Add(l); } // Lengthen slider curves that are too short compared to what's // in the .osu file. if (l < Length && calculatedPath.Count > 1) { Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2]; double d = diff.Length; if (d <= 0) return; calculatedPath[calculatedPath.Count - 1] += diff * (float)((Length - l) / d); cumulativeLength[calculatedPath.Count - 1] = Length; } } public void Calculate() { calculatePath(); calculateCumulativeLengthAndTrimPath(); } private int indexOfDistance(double d) { int i = cumulativeLength.BinarySearch(d); if (i < 0) i = ~i; return i; } private double progressToDistance(double progress) { return MathHelper.Clamp(progress, 0, 1) * Length; } private Vector2 interpolateVertices(int i, double d) { if (calculatedPath.Count == 0) return Vector2.Zero; if (i <= 0) return calculatedPath.First(); else if (i >= calculatedPath.Count) return calculatedPath.Last(); Vector2 p0 = calculatedPath[i - 1]; Vector2 p1 = calculatedPath[i]; double d0 = cumulativeLength[i - 1]; double d1 = cumulativeLength[i]; // Avoid division by and almost-zero number in case two points are extremely close to each other. if (Precision.AlmostEquals(d0, d1)) return p0; double w = (d - d0) / (d1 - d0); return p0 + (p1 - p0) * (float)w; } /// /// Computes the slider curve until a given progress that ranges from 0 (beginning of the slider) /// to 1 (end of the slider) and stores the generated path in the given list. /// /// The list to be filled with the computed curve. /// Ranges from 0 (beginning of the slider) to 1 (end of the slider). public void GetPathToProgress(List path, double p0, double p1) { double d0 = progressToDistance(p0); double d1 = progressToDistance(p1); path.Clear(); int i = 0; for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i); path.Add(interpolateVertices(i, d0)); for (; i < calculatedPath.Count && cumulativeLength[i] <= d1; ++i) path.Add(calculatedPath[i]); path.Add(interpolateVertices(i, d1)); } /// /// Computes the position on the slider at a given progress that ranges from 0 (beginning of the slider) /// to 1 (end of the slider). /// /// Ranges from 0 (beginning of the slider) to 1 (end of the slider). /// public Vector2 PositionAt(double progress) { double d = progressToDistance(progress); return interpolateVertices(indexOfDistance(d), d); } } }