1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 07:33:20 +08:00

Move path type correction

This is better because `PathControlPointVisualizer` is local to the editor, meaning there is no chance that this could affect gameplay.
This commit is contained in:
Naxess 2021-03-24 03:02:19 +01:00
parent 0bcd38e661
commit 4ae3eaaac6
3 changed files with 55 additions and 59 deletions

View File

@ -91,6 +91,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
}));
Connections.Add(new PathControlPointConnectionPiece(slider, e.NewStartingIndex + i));
point.Changed += updatePathTypes;
}
break;
@ -100,6 +102,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
Pieces.RemoveAll(p => p.ControlPoint == point);
Connections.RemoveAll(c => c.ControlPoint == point);
point.Changed -= updatePathTypes;
}
// If removing before the end of the path,
@ -142,6 +146,56 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
}
/// <summary>
/// Handles correction of invalid path types.
/// </summary>
private void updatePathTypes()
{
foreach (PathControlPoint segmentStartPoint in slider.Path.ControlPoints.Where(p => p.Type.Value != null))
{
if (segmentStartPoint.Type.Value != PathType.PerfectCurve)
continue;
Vector2[] points = slider.Path.PointsInSegment(segmentStartPoint).Select(p => p.Position.Value).ToArray();
if (points.Length == 3 && !validCircularArcSegment(points))
segmentStartPoint.Type.Value = PathType.Bezier;
}
}
/// <summary>
/// Returns whether the given points are arranged in a valid way. Invalid if points
/// are almost entirely linear - as this causes the radius to approach infinity,
/// which would exhaust memory when drawing / approximating.
/// </summary>
/// <param name="points">The three points that make up this circular arc segment.</param>
/// <returns></returns>
private bool validCircularArcSegment(IReadOnlyList<Vector2> points)
{
Vector2 a = points[0];
Vector2 b = points[1];
Vector2 c = points[2];
float maxLength = points.Max(p => p.Length);
Vector2 normA = new Vector2(a.X / maxLength, a.Y / maxLength);
Vector2 normB = new Vector2(b.X / maxLength, b.Y / maxLength);
Vector2 normC = new Vector2(c.X / maxLength, c.Y / maxLength);
float det = (normA.X - normB.X) * (normB.Y - normC.Y) - (normB.X - normC.X) * (normA.Y - normB.Y);
float acSq = (a - c).LengthSquared;
float abSq = (a - b).LengthSquared;
float bcSq = (b - c).LengthSquared;
// Exterior = curve wraps around the long way between end-points
// Exterior bottleneck is drawing-related, interior bottleneck is approximation-related,
// where the latter is much faster, hence differing thresholds
bool exterior = abSq > acSq || bcSq > acSq;
float threshold = exterior ? 0.05f : 0.001f;
return Math.Abs(det) >= threshold;
}
private void selectPiece(PathControlPointPiece piece, MouseButtonEvent e)
{
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)

View File

@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Objects
/// <summary>
/// Invoked when any property of this <see cref="PathControlPoint"/> is changed.
/// </summary>
internal event Action Changed;
public event Action Changed;
/// <summary>
/// Creates a new <see cref="PathControlPoint"/>.

View File

@ -54,21 +54,13 @@ namespace osu.Game.Rulesets.Objects
{
case NotifyCollectionChangedAction.Add:
foreach (var c in args.NewItems.Cast<PathControlPoint>())
{
c.Changed += invalidate;
c.Changed += updatePathTypes;
}
break;
case NotifyCollectionChangedAction.Reset:
case NotifyCollectionChangedAction.Remove:
foreach (var c in args.OldItems.Cast<PathControlPoint>())
{
c.Changed -= invalidate;
c.Changed -= updatePathTypes;
}
break;
}
@ -197,56 +189,6 @@ namespace osu.Game.Rulesets.Objects
return pointsInCurrentSegment;
}
/// <summary>
/// Handles correction of invalid path types.
/// </summary>
private void updatePathTypes()
{
foreach (PathControlPoint segmentStartPoint in ControlPoints.Where(p => p.Type.Value != null))
{
if (segmentStartPoint.Type.Value != PathType.PerfectCurve)
continue;
Vector2[] points = PointsInSegment(segmentStartPoint).Select(p => p.Position.Value).ToArray();
if (points.Length == 3 && !validCircularArcSegment(points))
segmentStartPoint.Type.Value = PathType.Bezier;
}
}
/// <summary>
/// Returns whether the given points are arranged in a valid way. Invalid if points
/// are almost entirely linear - as this causes the radius to approach infinity,
/// which would exhaust memory when drawing / approximating.
/// </summary>
/// <param name="points">The three points that make up this circular arc segment.</param>
/// <returns></returns>
private bool validCircularArcSegment(IReadOnlyList<Vector2> points)
{
Vector2 a = points[0];
Vector2 b = points[1];
Vector2 c = points[2];
float maxLength = points.Max(p => p.Length);
Vector2 normA = new Vector2(a.X / maxLength, a.Y / maxLength);
Vector2 normB = new Vector2(b.X / maxLength, b.Y / maxLength);
Vector2 normC = new Vector2(c.X / maxLength, c.Y / maxLength);
float det = (normA.X - normB.X) * (normB.Y - normC.Y) - (normB.X - normC.X) * (normA.Y - normB.Y);
float acSq = (a - c).LengthSquared;
float abSq = (a - b).LengthSquared;
float bcSq = (b - c).LengthSquared;
// Exterior = curve wraps around the long way between end-points
// Exterior bottleneck is drawing-related, interior bottleneck is approximation-related,
// where the latter is much faster, hence differing thresholds
bool exterior = abSq > acSq || bcSq > acSq;
float threshold = exterior ? 0.05f : 0.001f;
return Math.Abs(det) >= threshold;
}
private void invalidate()
{
pathCache.Invalidate();