diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index ce5dc4855e..0c9163ae41 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -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 { } + /// + /// Handles correction of invalid path types. + /// + 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; + } + } + + /// + /// 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. + /// + /// The three points that make up this circular arc segment. + /// + private bool validCircularArcSegment(IReadOnlyList 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) diff --git a/osu.Game/Rulesets/Objects/PathControlPoint.cs b/osu.Game/Rulesets/Objects/PathControlPoint.cs index f11917f4f4..2e4100ee0b 100644 --- a/osu.Game/Rulesets/Objects/PathControlPoint.cs +++ b/osu.Game/Rulesets/Objects/PathControlPoint.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Objects /// /// Invoked when any property of this is changed. /// - internal event Action Changed; + public event Action Changed; /// /// Creates a new . diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index c7931b440b..61f5f94142 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -54,21 +54,13 @@ namespace osu.Game.Rulesets.Objects { case NotifyCollectionChangedAction.Add: foreach (var c in args.NewItems.Cast()) - { c.Changed += invalidate; - c.Changed += updatePathTypes; - } - break; case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Remove: foreach (var c in args.OldItems.Cast()) - { c.Changed -= invalidate; - c.Changed -= updatePathTypes; - } - break; } @@ -197,56 +189,6 @@ namespace osu.Game.Rulesets.Objects return pointsInCurrentSegment; } - /// - /// Handles correction of invalid path types. - /// - 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; - } - } - - /// - /// 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. - /// - /// The three points that make up this circular arc segment. - /// - private bool validCircularArcSegment(IReadOnlyList 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();