2023-06-23 00:37:25 +08:00
|
|
|
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
2019-01-24 16:43:03 +08:00
|
|
|
|
// See the LICENCE file in the repository root for full licence text.
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2018-10-11 16:44:25 +08:00
|
|
|
|
using System;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
using System.Collections.Generic;
|
2020-02-17 14:06:14 +08:00
|
|
|
|
using System.Collections.Specialized;
|
2022-12-16 17:16:26 +08:00
|
|
|
|
using System.Diagnostics;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
using System.Linq;
|
2018-11-12 15:38:33 +08:00
|
|
|
|
using Newtonsoft.Json;
|
2019-12-05 16:49:32 +08:00
|
|
|
|
using osu.Framework.Bindables;
|
|
|
|
|
using osu.Framework.Caching;
|
2020-01-09 12:43:44 +08:00
|
|
|
|
using osu.Framework.Utils;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
using osu.Game.Rulesets.Objects.Types;
|
2018-11-20 15:51:59 +08:00
|
|
|
|
using osuTK;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
|
|
|
|
namespace osu.Game.Rulesets.Objects
|
|
|
|
|
{
|
2019-12-05 16:49:32 +08:00
|
|
|
|
public class SliderPath
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
2019-12-05 17:31:28 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The current version of this <see cref="SliderPath"/>. Updated when any change to the path occurs.
|
|
|
|
|
/// </summary>
|
2019-12-09 19:18:18 +08:00
|
|
|
|
[JsonIgnore]
|
2019-12-05 17:31:28 +08:00
|
|
|
|
public IBindable<int> Version => version;
|
|
|
|
|
|
|
|
|
|
private readonly Bindable<int> version = new Bindable<int>();
|
|
|
|
|
|
2018-11-12 13:16:21 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The user-set distance of the path. If non-null, <see cref="Distance"/> will match this value,
|
|
|
|
|
/// and the path will be shortened/lengthened to match this length.
|
|
|
|
|
/// </summary>
|
2019-12-05 16:49:54 +08:00
|
|
|
|
public readonly Bindable<double?> ExpectedDistance = new Bindable<double?>();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2023-12-20 04:20:21 +08:00
|
|
|
|
public bool HasValidLength => Precision.DefinitelyBigger(Distance, 0);
|
2021-04-16 14:22:32 +08:00
|
|
|
|
|
2018-11-12 15:38:14 +08:00
|
|
|
|
/// <summary>
|
2019-12-05 16:49:32 +08:00
|
|
|
|
/// The control points of the path.
|
2018-11-12 15:38:14 +08:00
|
|
|
|
/// </summary>
|
2019-12-05 16:49:32 +08:00
|
|
|
|
public readonly BindableList<PathControlPoint> ControlPoints = new BindableList<PathControlPoint>();
|
2018-11-12 15:38:14 +08:00
|
|
|
|
|
2019-12-05 16:49:32 +08:00
|
|
|
|
private readonly List<Vector2> calculatedPath = new List<Vector2>();
|
|
|
|
|
private readonly List<double> cumulativeLength = new List<double>();
|
2019-12-09 16:45:08 +08:00
|
|
|
|
private readonly Cached pathCache = new Cached();
|
|
|
|
|
|
2019-12-06 14:06:31 +08:00
|
|
|
|
private double calculatedLength;
|
2018-11-12 13:08:36 +08:00
|
|
|
|
|
2023-08-18 18:20:40 +08:00
|
|
|
|
private readonly List<int> segmentEnds = new List<int>();
|
|
|
|
|
private double[] segmentEndDistances = Array.Empty<double>();
|
|
|
|
|
|
2018-11-12 13:16:21 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Creates a new <see cref="SliderPath"/>.
|
|
|
|
|
/// </summary>
|
2019-12-05 17:19:42 +08:00
|
|
|
|
public SliderPath()
|
2018-11-01 14:38:19 +08:00
|
|
|
|
{
|
2019-12-05 17:31:28 +08:00
|
|
|
|
ExpectedDistance.ValueChanged += _ => invalidate();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2020-02-17 14:06:14 +08:00
|
|
|
|
ControlPoints.CollectionChanged += (_, args) =>
|
2019-12-05 16:49:32 +08:00
|
|
|
|
{
|
2020-02-17 14:06:14 +08:00
|
|
|
|
switch (args.Action)
|
|
|
|
|
{
|
|
|
|
|
case NotifyCollectionChangedAction.Add:
|
2022-12-16 17:16:26 +08:00
|
|
|
|
Debug.Assert(args.NewItems != null);
|
|
|
|
|
|
2024-02-17 20:46:38 +08:00
|
|
|
|
foreach (object? newItem in args.NewItems)
|
|
|
|
|
((PathControlPoint)newItem).Changed += invalidate;
|
|
|
|
|
|
2020-02-17 14:06:14 +08:00
|
|
|
|
break;
|
2018-10-26 13:18:48 +08:00
|
|
|
|
|
2020-10-09 05:31:59 +08:00
|
|
|
|
case NotifyCollectionChangedAction.Reset:
|
2020-02-17 14:06:14 +08:00
|
|
|
|
case NotifyCollectionChangedAction.Remove:
|
2022-12-16 17:16:26 +08:00
|
|
|
|
Debug.Assert(args.OldItems != null);
|
|
|
|
|
|
2024-02-17 20:46:38 +08:00
|
|
|
|
foreach (object? oldItem in args.OldItems)
|
|
|
|
|
((PathControlPoint)oldItem).Changed -= invalidate;
|
2020-02-17 14:06:14 +08:00
|
|
|
|
break;
|
|
|
|
|
}
|
2019-12-05 16:49:32 +08:00
|
|
|
|
|
2019-12-05 17:31:28 +08:00
|
|
|
|
invalidate();
|
2019-12-05 16:49:32 +08:00
|
|
|
|
};
|
2019-12-05 17:19:42 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2019-12-09 16:45:08 +08:00
|
|
|
|
/// Creates a new <see cref="SliderPath"/> initialised with a list of control points.
|
2019-12-05 17:19:42 +08:00
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="controlPoints">An optional set of <see cref="PathControlPoint"/>s to initialise the path with.</param>
|
|
|
|
|
/// <param name="expectedDistance">A user-set distance of the path that may be shorter or longer than the true distance between all control points.
|
|
|
|
|
/// The path will be shortened/lengthened to match this length. If null, the path will use the true distance between all control points.</param>
|
|
|
|
|
[JsonConstructor]
|
|
|
|
|
public SliderPath(PathControlPoint[] controlPoints, double? expectedDistance = null)
|
|
|
|
|
: this()
|
|
|
|
|
{
|
2019-12-05 16:49:32 +08:00
|
|
|
|
ControlPoints.AddRange(controlPoints);
|
2019-12-05 17:19:42 +08:00
|
|
|
|
ExpectedDistance.Value = expectedDistance;
|
|
|
|
|
}
|
2019-12-05 16:49:32 +08:00
|
|
|
|
|
2019-12-05 17:19:42 +08:00
|
|
|
|
public SliderPath(PathType type, Vector2[] controlPoints, double? expectedDistance = null)
|
2022-06-24 13:48:43 +08:00
|
|
|
|
: this(controlPoints.Select((c, i) => new PathControlPoint(c, i == 0 ? type : null)).ToArray(), expectedDistance)
|
2019-12-05 17:19:42 +08:00
|
|
|
|
{
|
2018-11-12 16:10:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
2018-11-12 13:16:21 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The distance of the path after lengthening/shortening to account for <see cref="ExpectedDistance"/>.
|
|
|
|
|
/// </summary>
|
2018-11-12 15:38:39 +08:00
|
|
|
|
[JsonIgnore]
|
2018-11-12 15:38:14 +08:00
|
|
|
|
public double Distance
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
2019-12-05 16:49:32 +08:00
|
|
|
|
ensureValid();
|
2019-12-14 20:54:22 +08:00
|
|
|
|
return cumulativeLength.Count == 0 ? 0 : cumulativeLength[^1];
|
2018-11-12 15:38:14 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
2018-11-12 13:08:36 +08:00
|
|
|
|
|
2019-12-06 14:53:19 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// The distance of the path prior to lengthening/shortening to account for <see cref="ExpectedDistance"/>.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public double CalculatedDistance
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
ensureValid();
|
|
|
|
|
return calculatedLength;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-12 13:08:36 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Computes the slider path 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.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="path">The list to be filled with the computed path.</param>
|
|
|
|
|
/// <param name="p0">Start progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider).</param>
|
|
|
|
|
/// <param name="p1">End progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider).</param>
|
|
|
|
|
public void GetPathToProgress(List<Vector2> path, double p0, double p1)
|
|
|
|
|
{
|
2019-12-05 16:49:32 +08:00
|
|
|
|
ensureValid();
|
2018-11-12 15:38:14 +08:00
|
|
|
|
|
2018-11-12 13:08:36 +08:00
|
|
|
|
double d0 = progressToDistance(p0);
|
|
|
|
|
double d1 = progressToDistance(p1);
|
|
|
|
|
|
|
|
|
|
path.Clear();
|
|
|
|
|
|
|
|
|
|
int i = 0;
|
2019-04-01 11:16:05 +08:00
|
|
|
|
|
2018-11-12 13:08:36 +08:00
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Computes the position on the slider at a given progress that ranges from 0 (beginning of the path)
|
|
|
|
|
/// to 1 (end of the path).
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="progress">Ranges from 0 (beginning of the path) to 1 (end of the path).</param>
|
|
|
|
|
public Vector2 PositionAt(double progress)
|
|
|
|
|
{
|
2019-12-05 16:49:32 +08:00
|
|
|
|
ensureValid();
|
2018-11-12 15:38:14 +08:00
|
|
|
|
|
2018-11-12 13:08:36 +08:00
|
|
|
|
double d = progressToDistance(progress);
|
|
|
|
|
return interpolateVertices(indexOfDistance(d), d);
|
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2021-03-22 22:55:30 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns the control points belonging to the same segment as the one given.
|
|
|
|
|
/// The first point has a PathType which all other points inherit.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="controlPoint">One of the control points in the segment.</param>
|
|
|
|
|
public List<PathControlPoint> PointsInSegment(PathControlPoint controlPoint)
|
|
|
|
|
{
|
|
|
|
|
bool found = false;
|
|
|
|
|
List<PathControlPoint> pointsInCurrentSegment = new List<PathControlPoint>();
|
2021-03-23 02:03:55 +08:00
|
|
|
|
|
2021-03-22 22:55:30 +08:00
|
|
|
|
foreach (PathControlPoint point in ControlPoints)
|
|
|
|
|
{
|
2021-08-26 00:42:57 +08:00
|
|
|
|
if (point.Type != null)
|
2021-03-22 22:55:30 +08:00
|
|
|
|
{
|
|
|
|
|
if (!found)
|
|
|
|
|
pointsInCurrentSegment.Clear();
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
pointsInCurrentSegment.Add(point);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pointsInCurrentSegment.Add(point);
|
|
|
|
|
|
|
|
|
|
if (point == controlPoint)
|
|
|
|
|
found = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pointsInCurrentSegment;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-07 16:51:22 +08:00
|
|
|
|
/// <summary>
|
2023-08-20 01:39:29 +08:00
|
|
|
|
/// Returns the progress values at which (control point) segments of the path end.
|
|
|
|
|
/// Ranges from 0 (beginning of the path) to 1 (end of the path) to infinity (beyond the end of the path).
|
2022-12-07 16:51:22 +08:00
|
|
|
|
/// </summary>
|
2023-08-21 14:23:58 +08:00
|
|
|
|
/// <remarks>
|
|
|
|
|
/// <see cref="PositionAt"/> truncates the progression values to [0,1],
|
|
|
|
|
/// so you can't use this method in conjunction with that one to retrieve the positions of segment ends beyond the end of the path.
|
|
|
|
|
/// </remarks>
|
|
|
|
|
/// <example>
|
|
|
|
|
/// <para>
|
|
|
|
|
/// In case <see cref="Distance"/> is less than <see cref="CalculatedDistance"/>,
|
2023-08-20 01:39:29 +08:00
|
|
|
|
/// the last segment ends after the end of the path, hence it returns a value greater than 1.
|
2023-08-21 14:23:58 +08:00
|
|
|
|
/// </para>
|
|
|
|
|
/// <para>
|
2023-08-20 01:39:29 +08:00
|
|
|
|
/// In case <see cref="Distance"/> is greater than <see cref="CalculatedDistance"/>,
|
2023-08-21 14:23:58 +08:00
|
|
|
|
/// the last segment ends before the end of the path, hence it returns a value less than 1.
|
|
|
|
|
/// </para>
|
|
|
|
|
/// </example>
|
2022-12-07 16:51:22 +08:00
|
|
|
|
public IEnumerable<double> GetSegmentEnds()
|
|
|
|
|
{
|
|
|
|
|
ensureValid();
|
|
|
|
|
|
2023-08-18 18:20:40 +08:00
|
|
|
|
return segmentEndDistances.Select(d => d / Distance);
|
2022-12-07 16:51:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-05 17:31:28 +08:00
|
|
|
|
private void invalidate()
|
|
|
|
|
{
|
|
|
|
|
pathCache.Invalidate();
|
|
|
|
|
version.Value++;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-05 16:49:32 +08:00
|
|
|
|
private void ensureValid()
|
2018-11-12 15:38:14 +08:00
|
|
|
|
{
|
2019-12-05 16:49:32 +08:00
|
|
|
|
if (pathCache.IsValid)
|
2018-11-12 15:38:14 +08:00
|
|
|
|
return;
|
2019-02-28 12:31:40 +08:00
|
|
|
|
|
2018-11-12 15:38:14 +08:00
|
|
|
|
calculatePath();
|
2019-12-06 14:06:31 +08:00
|
|
|
|
calculateLength();
|
2019-12-05 16:49:32 +08:00
|
|
|
|
|
|
|
|
|
pathCache.Validate();
|
2018-11-12 15:38:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-05 16:49:32 +08:00
|
|
|
|
private void calculatePath()
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
2019-12-05 16:49:32 +08:00
|
|
|
|
calculatedPath.Clear();
|
2022-12-07 16:51:22 +08:00
|
|
|
|
segmentEnds.Clear();
|
2019-04-01 11:16:05 +08:00
|
|
|
|
|
2019-12-05 16:49:32 +08:00
|
|
|
|
if (ControlPoints.Count == 0)
|
|
|
|
|
return;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2019-12-05 16:49:32 +08:00
|
|
|
|
Vector2[] vertices = new Vector2[ControlPoints.Count];
|
|
|
|
|
for (int i = 0; i < ControlPoints.Count; i++)
|
2021-08-26 00:42:57 +08:00
|
|
|
|
vertices[i] = ControlPoints[i].Position;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2019-12-05 16:49:32 +08:00
|
|
|
|
int start = 0;
|
2019-04-01 11:16:05 +08:00
|
|
|
|
|
2019-12-05 16:49:32 +08:00
|
|
|
|
for (int i = 0; i < ControlPoints.Count; i++)
|
|
|
|
|
{
|
2021-08-26 00:42:57 +08:00
|
|
|
|
if (ControlPoints[i].Type == null && i < ControlPoints.Count - 1)
|
2019-12-05 16:49:32 +08:00
|
|
|
|
continue;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2019-12-05 16:49:32 +08:00
|
|
|
|
// The current vertex ends the segment
|
|
|
|
|
var segmentVertices = vertices.AsSpan().Slice(start, i - start + 1);
|
2023-11-08 18:43:54 +08:00
|
|
|
|
var segmentType = ControlPoints[start].Type ?? PathType.LINEAR;
|
2023-09-19 14:28:28 +08:00
|
|
|
|
|
2023-09-19 13:31:26 +08:00
|
|
|
|
// No need to calculate path when there is only 1 vertex
|
|
|
|
|
if (segmentVertices.Length == 1)
|
|
|
|
|
calculatedPath.Add(segmentVertices[0]);
|
|
|
|
|
else if (segmentVertices.Length > 1)
|
2019-12-05 16:49:32 +08:00
|
|
|
|
{
|
2023-09-19 14:28:28 +08:00
|
|
|
|
List<Vector2> subPath = calculateSubPath(segmentVertices, segmentType);
|
2023-09-19 13:31:26 +08:00
|
|
|
|
// Skip the first vertex if it is the same as the last vertex from the previous segment
|
2024-02-17 20:46:38 +08:00
|
|
|
|
bool skipFirst = calculatedPath.Count > 0 && subPath.Count > 0 && calculatedPath.Last() == subPath[0];
|
2023-09-19 14:28:28 +08:00
|
|
|
|
|
2024-02-17 20:46:38 +08:00
|
|
|
|
for (int j = skipFirst ? 1 : 0; j < subPath.Count; j++)
|
|
|
|
|
calculatedPath.Add(subPath[j]);
|
2019-12-05 16:49:32 +08:00
|
|
|
|
}
|
2018-10-11 16:44:25 +08:00
|
|
|
|
|
2023-08-18 18:20:40 +08:00
|
|
|
|
if (i > 0)
|
2023-08-19 20:34:20 +08:00
|
|
|
|
{
|
2023-08-18 18:20:40 +08:00
|
|
|
|
// Remember the index of the segment end
|
|
|
|
|
segmentEnds.Add(calculatedPath.Count - 1);
|
2023-08-19 20:34:20 +08:00
|
|
|
|
}
|
2022-12-07 16:51:22 +08:00
|
|
|
|
|
2019-12-05 16:49:32 +08:00
|
|
|
|
// Start the new segment at the current vertex
|
|
|
|
|
start = i;
|
|
|
|
|
}
|
2019-12-09 17:25:13 +08:00
|
|
|
|
}
|
2018-10-11 16:44:25 +08:00
|
|
|
|
|
2019-12-09 17:25:13 +08:00
|
|
|
|
private List<Vector2> calculateSubPath(ReadOnlySpan<Vector2> subControlPoints, PathType type)
|
|
|
|
|
{
|
2023-11-11 22:02:06 +08:00
|
|
|
|
switch (type.Type)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
2023-11-08 18:43:54 +08:00
|
|
|
|
case SplineType.Linear:
|
2023-11-11 17:45:22 +08:00
|
|
|
|
return PathApproximator.LinearToPiecewiseLinear(subControlPoints);
|
2019-12-05 16:49:32 +08:00
|
|
|
|
|
2023-11-08 18:43:54 +08:00
|
|
|
|
case SplineType.PerfectCurve:
|
2019-12-09 17:25:13 +08:00
|
|
|
|
if (subControlPoints.Length != 3)
|
|
|
|
|
break;
|
2018-10-11 16:44:25 +08:00
|
|
|
|
|
2023-11-11 17:45:22 +08:00
|
|
|
|
List<Vector2> subPath = PathApproximator.CircularArcToPiecewiseLinear(subControlPoints);
|
2018-10-11 16:44:25 +08:00
|
|
|
|
|
2021-03-22 22:54:33 +08:00
|
|
|
|
// If for some reason a circular arc could not be fit to the 3 given points, fall back to a numerically stable bezier approximation.
|
2021-12-10 13:15:00 +08:00
|
|
|
|
if (subPath.Count == 0)
|
2019-12-09 17:25:13 +08:00
|
|
|
|
break;
|
2019-12-05 16:49:32 +08:00
|
|
|
|
|
2021-12-10 13:15:00 +08:00
|
|
|
|
return subPath;
|
2019-12-05 16:49:32 +08:00
|
|
|
|
|
2023-11-08 18:43:54 +08:00
|
|
|
|
case SplineType.Catmull:
|
2023-11-11 17:45:22 +08:00
|
|
|
|
return PathApproximator.CatmullToPiecewiseLinear(subControlPoints);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
}
|
2019-12-09 17:25:13 +08:00
|
|
|
|
|
2023-11-11 17:45:22 +08:00
|
|
|
|
return PathApproximator.BSplineToPiecewiseLinear(subControlPoints, type.Degree ?? subControlPoints.Length);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-06 14:06:31 +08:00
|
|
|
|
private void calculateLength()
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
2019-12-06 14:06:31 +08:00
|
|
|
|
calculatedLength = 0;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
cumulativeLength.Clear();
|
2019-12-06 14:06:31 +08:00
|
|
|
|
cumulativeLength.Add(0);
|
2019-12-05 16:49:54 +08:00
|
|
|
|
|
2019-12-06 14:06:31 +08:00
|
|
|
|
for (int i = 0; i < calculatedPath.Count - 1; i++)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
|
|
|
|
Vector2 diff = calculatedPath[i + 1] - calculatedPath[i];
|
2019-12-06 14:06:31 +08:00
|
|
|
|
calculatedLength += diff.Length;
|
|
|
|
|
cumulativeLength.Add(calculatedLength);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-18 18:20:40 +08:00
|
|
|
|
// Store the distances of the segment ends now, because after shortening the indices may be out of range
|
|
|
|
|
segmentEndDistances = new double[segmentEnds.Count];
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < segmentEnds.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
segmentEndDistances[i] = cumulativeLength[segmentEnds[i]];
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-06 14:06:31 +08:00
|
|
|
|
if (ExpectedDistance.Value is double expectedDistance && calculatedLength != expectedDistance)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
{
|
2023-09-18 23:40:00 +08:00
|
|
|
|
// In osu-stable, if the last two path points of a slider are equal, extension is not performed.
|
|
|
|
|
if (calculatedPath.Count >= 2 && calculatedPath[^1] == calculatedPath[^2] && expectedDistance > calculatedLength)
|
2021-10-26 14:46:15 +08:00
|
|
|
|
{
|
|
|
|
|
cumulativeLength.Add(calculatedLength);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-06 14:06:31 +08:00
|
|
|
|
// The last length is always incorrect
|
|
|
|
|
cumulativeLength.RemoveAt(cumulativeLength.Count - 1);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2019-12-06 14:06:31 +08:00
|
|
|
|
int pathEndIndex = calculatedPath.Count - 1;
|
|
|
|
|
|
|
|
|
|
if (calculatedLength > expectedDistance)
|
|
|
|
|
{
|
|
|
|
|
// The path will be shortened further, in which case we should trim any more unnecessary lengths and their associated path segments
|
2019-12-14 20:54:22 +08:00
|
|
|
|
while (cumulativeLength.Count > 0 && cumulativeLength[^1] >= expectedDistance)
|
2019-12-06 14:06:31 +08:00
|
|
|
|
{
|
|
|
|
|
cumulativeLength.RemoveAt(cumulativeLength.Count - 1);
|
|
|
|
|
calculatedPath.RemoveAt(pathEndIndex--);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pathEndIndex <= 0)
|
|
|
|
|
{
|
|
|
|
|
// The expected distance is negative or zero
|
|
|
|
|
// TODO: Perhaps negative path lengths should be disallowed altogether
|
2019-12-06 14:37:00 +08:00
|
|
|
|
cumulativeLength.Add(0);
|
2018-04-13 17:19:50 +08:00
|
|
|
|
return;
|
2019-12-06 14:06:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The direction of the segment to shorten or lengthen
|
|
|
|
|
Vector2 dir = (calculatedPath[pathEndIndex] - calculatedPath[pathEndIndex - 1]).Normalized();
|
2018-04-13 17:19:50 +08:00
|
|
|
|
|
2019-12-14 20:54:22 +08:00
|
|
|
|
calculatedPath[pathEndIndex] = calculatedPath[pathEndIndex - 1] + dir * (float)(expectedDistance - cumulativeLength[^1]);
|
2019-12-06 14:06:31 +08:00
|
|
|
|
cumulativeLength.Add(expectedDistance);
|
2018-10-29 14:36:43 +08:00
|
|
|
|
}
|
2018-04-13 17:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int indexOfDistance(double d)
|
|
|
|
|
{
|
|
|
|
|
int i = cumulativeLength.BinarySearch(d);
|
|
|
|
|
if (i < 0) i = ~i;
|
|
|
|
|
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private double progressToDistance(double progress)
|
|
|
|
|
{
|
2019-11-20 20:19:49 +08:00
|
|
|
|
return Math.Clamp(progress, 0, 1) * Distance;
|
2018-04-13 17:19:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Vector2 interpolateVertices(int i, double d)
|
|
|
|
|
{
|
|
|
|
|
if (calculatedPath.Count == 0)
|
|
|
|
|
return Vector2.Zero;
|
|
|
|
|
|
|
|
|
|
if (i <= 0)
|
|
|
|
|
return calculatedPath.First();
|
2018-11-01 14:38:19 +08:00
|
|
|
|
if (i >= calculatedPath.Count)
|
2018-04-13 17:19:50 +08:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|