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();