diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs index 5e68acde94..cac1356c81 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Tests Vector2.Zero, new Vector2(width * CatchPlayfield.BASE_WIDTH, 0) }, - CurveType = CurveType.Linear, + PathType = PathType.Linear, Distance = width * CatchPlayfield.BASE_WIDTH, StartTime = i * 2000, NewCombo = i % 8 == 0 diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 15d4edc411..a178748bd5 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps StartTime = obj.StartTime, Samples = obj.Samples, ControlPoints = curveData.ControlPoints, - CurveType = curveData.CurveType, + PathType = curveData.PathType, Distance = curveData.Distance, RepeatSamples = curveData.RepeatSamples, RepeatCount = curveData.RepeatCount, diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 82e32d24d2..da581d9619 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Catch.Objects if (TickDistance == 0) return; - var length = Curve.Distance; + var length = Path.Distance; var tickDistance = Math.Min(TickDistance, length); var spanDuration = length / Velocity; @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Catch.Objects AddNested(new TinyDroplet { StartTime = t, - X = X + Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, + X = X + Path.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, Samples = new List(Samples.Select(s => new SampleInfo { Bank = s.Bank, @@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Catch.Objects AddNested(new Droplet { StartTime = time, - X = X + Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, + X = X + Path.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, Samples = new List(Samples.Select(s => new SampleInfo { Bank = s.Bank, @@ -127,12 +127,12 @@ namespace osu.Game.Rulesets.Catch.Objects { Samples = Samples, StartTime = spanStartTime + spanDuration, - X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH + X = X + Path.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH }); } } - public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; + public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; @@ -140,24 +140,24 @@ namespace osu.Game.Rulesets.Catch.Objects public double Distance { - get { return Curve.Distance; } - set { Curve.Distance = value; } + get { return Path.Distance; } + set { Path.Distance = value; } } - public SliderCurve Curve { get; } = new SliderCurve(); + public SliderPath Path { get; } = new SliderPath(); public Vector2[] ControlPoints { - get { return Curve.ControlPoints; } - set { Curve.ControlPoints = value; } + get { return Path.ControlPoints; } + set { Path.ControlPoints = value; } } public List> RepeatSamples { get; set; } = new List>(); - public CurveType CurveType + public PathType PathType { - get { return Curve.CurveType; } - set { Curve.CurveType = value; } + get { return Path.PathType; } + set { Path.PathType = value; } } public double? LegacyLastTickOffset { get; set; } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs index 300ac16155..4c0385deda 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - CurveType = CurveType.Linear, + PathType = PathType.Linear, StartTime = Time.Current + 1000, Position = new Vector2(-200, 0), ControlPoints = new[] @@ -207,7 +207,7 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - CurveType = CurveType.Bezier, + PathType = PathType.Bezier, StartTime = Time.Current + 1000, Position = new Vector2(-200, 0), ControlPoints = new[] @@ -232,7 +232,7 @@ namespace osu.Game.Rulesets.Osu.Tests { var slider = new Slider { - CurveType = CurveType.Linear, + PathType = PathType.Linear, StartTime = Time.Current + 1000, Position = new Vector2(0, 0), ControlPoints = new[] @@ -264,7 +264,7 @@ namespace osu.Game.Rulesets.Osu.Tests { StartTime = Time.Current + 1000, Position = new Vector2(-100, 0), - CurveType = CurveType.Catmull, + PathType = PathType.Catmull, ControlPoints = new[] { Vector2.Zero, diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionMask.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionMask.cs index 5e68d5cdc9..87e0e1a7ec 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionMask.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionMask.cs @@ -1,11 +1,14 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks; +using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; @@ -15,6 +18,16 @@ namespace osu.Game.Rulesets.Osu.Tests { public class TestCaseSliderSelectionMask : HitObjectSelectionMaskTestCase { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(SliderSelectionMask), + typeof(SliderCircleSelectionMask), + typeof(SliderBodyPiece), + typeof(SliderCircle), + typeof(PathControlPointVisualiser), + typeof(PathControlPointPiece) + }; + private readonly DrawableSlider drawableObject; public TestCaseSliderSelectionMask() @@ -28,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Tests new Vector2(150, 150), new Vector2(300, 0) }, - CurveType = CurveType.Bezier, + PathType = PathType.Bezier, Distance = 350 }; diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index b2914d4b82..9f432fc31a 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps StartTime = original.StartTime, Samples = original.Samples, ControlPoints = curveData.ControlPoints, - CurveType = curveData.CurveType, + PathType = curveData.PathType, Distance = curveData.Distance, RepeatSamples = curveData.RepeatSamples, RepeatCount = curveData.RepeatCount, diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index d6684f55af..39e3c009da 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing progress = progress % 1; // ReSharper disable once PossibleInvalidOperationException (bugged in current r# version) - var diff = slider.StackedPosition + slider.Curve.PositionAt(progress) - slider.LazyEndPosition.Value; + var diff = slider.StackedPosition + slider.Path.PositionAt(progress) - slider.LazyEndPosition.Value; float dist = diff.Length; if (dist > approxFollowCircleRadius) diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/PathControlPointPiece.cs new file mode 100644 index 0000000000..70156578b4 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/PathControlPointPiece.cs @@ -0,0 +1,113 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Lines; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Rulesets.Osu.Objects; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components +{ + public class PathControlPointPiece : CompositeDrawable + { + private readonly Slider slider; + private readonly int index; + + private readonly Path path; + private readonly CircularContainer marker; + + [Resolved] + private OsuColour colours { get; set; } + + public PathControlPointPiece(Slider slider, int index) + { + this.slider = slider; + this.index = index; + + Origin = Anchor.Centre; + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + path = new SmoothPath + { + Anchor = Anchor.Centre, + PathWidth = 1 + }, + marker = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(10), + Masking = true, + Child = new Box { RelativeSizeAxes = Axes.Both } + } + }; + } + + protected override void Update() + { + base.Update(); + + Position = slider.StackedPosition + slider.ControlPoints[index]; + + marker.Colour = isSegmentSeparator ? colours.Red : colours.Yellow; + + path.ClearVertices(); + + if (index != slider.ControlPoints.Length - 1) + { + path.AddVertex(Vector2.Zero); + path.AddVertex(slider.ControlPoints[index + 1] - slider.ControlPoints[index]); + } + + path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos); + + protected override bool OnDragStart(DragStartEvent e) => true; + + protected override bool OnDrag(DragEvent e) + { + var newControlPoints = slider.ControlPoints.ToArray(); + + if (index == 0) + { + // Special handling for the head - only the position of the slider changes + slider.Position += e.Delta; + + // Since control points are relative to the position of the slider, they all need to be offset backwards by the delta + for (int i = 1; i < newControlPoints.Length; i++) + newControlPoints[i] -= e.Delta; + } + else + newControlPoints[index] += e.Delta; + + if (isSegmentSeparatorWithNext) + newControlPoints[index + 1] = newControlPoints[index]; + + if (isSegmentSeparatorWithPrevious) + newControlPoints[index - 1] = newControlPoints[index]; + + slider.ControlPoints = newControlPoints; + slider.Path.Calculate(true); + + return true; + } + + protected override bool OnDragEnd(DragEndEvent e) => true; + + private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious; + + private bool isSegmentSeparatorWithNext => index < slider.ControlPoints.Length - 1 && slider.ControlPoints[index + 1] == slider.ControlPoints[index]; + + private bool isSegmentSeparatorWithPrevious => index > 0 && slider.ControlPoints[index - 1] == slider.ControlPoints[index]; + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/PathControlPointVisualiser.cs new file mode 100644 index 0000000000..1d25f8cd39 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/PathControlPointVisualiser.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Osu.Objects; + +namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components +{ + public class PathControlPointVisualiser : CompositeDrawable + { + private readonly Slider slider; + + private readonly Container pieces; + + public PathControlPointVisualiser(Slider slider) + { + this.slider = slider; + + InternalChild = pieces = new Container { RelativeSizeAxes = Axes.Both }; + + slider.ControlPointsChanged += _ => updatePathControlPoints(); + updatePathControlPoints(); + } + + private void updatePathControlPoints() + { + while (slider.ControlPoints.Length > pieces.Count) + pieces.Add(new PathControlPointPiece(slider, pieces.Count)); + while (slider.ControlPoints.Length < pieces.Count) + pieces.Remove(pieces[pieces.Count - 1]); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderBodyPiece.cs index 3123a4fcea..46d99e1740 100644 --- a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderBodyPiece.cs @@ -46,6 +46,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components // Need to cause one update body.UpdateProgress(0); + body.Refresh(); } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderCirclePiece.cs index c5ecde5c4c..7864429d93 100644 --- a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/Components/SliderCirclePiece.cs @@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components { this.slider = slider; this.position = position; + + slider.ControlPointsChanged += _ => UpdatePosition(); } protected override void UpdatePosition() @@ -23,10 +25,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components switch (position) { case SliderPosition.Start: - Position = slider.StackedPosition + slider.Curve.PositionAt(0); + Position = slider.StackedPosition + slider.Path.PositionAt(0); break; case SliderPosition.End: - Position = slider.StackedPosition + slider.Curve.PositionAt(1); + Position = slider.StackedPosition + slider.Path.PositionAt(1); break; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderSelectionMask.cs b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderSelectionMask.cs index a411064f68..b79b0ba1fb 100644 --- a/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderSelectionMask.cs +++ b/osu.Game.Rulesets.Osu/Edit/Masks/SliderMasks/SliderSelectionMask.cs @@ -24,6 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks new SliderBodyPiece(sliderObject), headMask = new SliderCircleSelectionMask(slider.HeadCircle, sliderObject, SliderPosition.Start), new SliderCircleSelectionMask(slider.TailCircle, sliderObject, SliderPosition.End), + new PathControlPointVisualiser(sliderObject), }; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index 1b3725a15e..e01d71e1f8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Mods newControlPoints[i] = new Vector2(slider.ControlPoints[i].X, -slider.ControlPoints[i].Y); slider.ControlPoints = newControlPoints; - slider.Curve?.Calculate(); // Recalculate the slider curve + slider.Path?.Calculate(); // Recalculate the slider curve } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 16bd522c1d..a137343cfe 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -85,6 +85,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } HitObject.PositionChanged += _ => Position = HitObject.StackedPosition; + + slider.ControlPointsChanged += _ => Body.Refresh(); } public override Color4 AccentColour @@ -119,7 +121,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); foreach (var c in components.OfType()) c.UpdateProgress(completionProgress); - foreach (var c in components.OfType()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0)); + foreach (var c in components.OfType()) c.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0)); foreach (var t in components.OfType()) t.Tracking = Ball.Tracking; Size = Body.Size; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index 6d6cba4936..6a836679a2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -16,7 +16,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { this.slider = slider; - Position = HitObject.Position - slider.Position; + h.PositionChanged += _ => updatePosition(); + slider.ControlPointsChanged += _ => updatePosition(); + + updatePosition(); } protected override void Update() @@ -33,5 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Action OnShake; protected override void Shake(double maximumLength) => OnShake?.Invoke(maximumLength); + + private void updatePosition() => Position = HitObject.Position - slider.Position; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 45c925b87a..cc88a6718b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -8,6 +8,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking { + private readonly Slider slider; + /// /// The judgement text is provided by the . /// @@ -18,6 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSliderTail(Slider slider, SliderTailCircle hitCircle) : base(hitCircle) { + this.slider = slider; + Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; @@ -25,7 +29,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AlwaysPresent = true; - Position = HitObject.Position - slider.Position; + hitCircle.PositionChanged += _ => updatePosition(); + slider.ControlPointsChanged += _ => updatePosition(); + + updatePosition(); } protected override void CheckForResult(bool userTriggered, double timeOffset) @@ -33,5 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!userTriggered && timeOffset >= 0) ApplyResult(r => r.Type = Tracking ? HitResult.Great : HitResult.Miss); } + + private void updatePosition() => Position = HitObject.Position - slider.Position; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs index 09d6f9459a..0e6f3ad16c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs @@ -45,15 +45,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces [BackgroundDependencyLoader] private void load() { - // Generate the entire curve - slider.Curve.GetPathToProgress(CurrentCurve, 0, 1); - SetVertices(CurrentCurve); - - // The body is sized to the full path size to avoid excessive autosize computations - Size = Path.Size; - - snakedPosition = Path.PositionInBoundingBox(Vector2.Zero); - snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]); + Refresh(); } public void UpdateProgress(double completionProgress) @@ -80,6 +72,27 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces setRange(start, end); } + public void Refresh() + { + // Generate the entire curve + slider.Path.GetPathToProgress(CurrentCurve, 0, 1); + SetVertices(CurrentCurve); + + // The body is sized to the full path size to avoid excessive autosize computations + Size = Path.Size; + + snakedPosition = Path.PositionInBoundingBox(Vector2.Zero); + snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]); + + var lastSnakedStart = SnakedStart ?? 0; + var lastSnakedEnd = SnakedEnd ?? 0; + + SnakedStart = null; + SnakedEnd = null; + + setRange(lastSnakedStart, lastSnakedEnd); + } + private void setRange(double p0, double p1) { if (p0 > p1) @@ -90,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces SnakedStart = p0; SnakedEnd = p1; - slider.Curve.GetPathToProgress(CurrentCurve, p0, p1); + slider.Path.GetPathToProgress(CurrentCurve, p0, p1); SetVertices(CurrentCurve); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index a6f5bdb24e..b7240991d4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -22,7 +22,9 @@ namespace osu.Game.Rulesets.Osu.Objects /// private const float base_scoring_distance = 100; - public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; + public event Action ControlPointsChanged; + + public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public double Duration => EndTime - StartTime; public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); @@ -50,24 +52,34 @@ namespace osu.Game.Rulesets.Osu.Objects } } - public SliderCurve Curve { get; } = new SliderCurve(); + public SliderPath Path { get; } = new SliderPath(); public Vector2[] ControlPoints { - get { return Curve.ControlPoints; } - set { Curve.ControlPoints = value; } + get => Path.ControlPoints; + set + { + if (Path.ControlPoints == value) + return; + Path.ControlPoints = value; + + ControlPointsChanged?.Invoke(value); + + if (TailCircle != null) + TailCircle.Position = EndPosition; + } } - public CurveType CurveType + public PathType PathType { - get { return Curve.CurveType; } - set { Curve.CurveType = value; } + get { return Path.PathType; } + set { Path.PathType = value; } } public double Distance { - get { return Curve.Distance; } - set { Curve.Distance = value; } + get { return Path.Distance; } + set { Path.Distance = value; } } public override Vector2 Position @@ -177,7 +189,7 @@ namespace osu.Game.Rulesets.Osu.Objects private void createTicks() { - var length = Curve.Distance; + var length = Path.Distance; var tickDistance = MathHelper.Clamp(TickDistance, 0, length); if (tickDistance == 0) return; @@ -216,7 +228,7 @@ namespace osu.Game.Rulesets.Osu.Objects SpanIndex = span, SpanStartTime = spanStartTime, StartTime = spanStartTime + timeProgress * SpanDuration, - Position = Position + Curve.PositionAt(distanceProgress), + Position = Position + Path.PositionAt(distanceProgress), StackHeight = StackHeight, Scale = Scale, Samples = sampleList @@ -234,7 +246,7 @@ namespace osu.Game.Rulesets.Osu.Objects RepeatIndex = repeatIndex, SpanDuration = SpanDuration, StartTime = StartTime + repeat * SpanDuration, - Position = Position + Curve.PositionAt(repeat % 2), + Position = Position + Path.PositionAt(repeat % 2), StackHeight = StackHeight, Scale = Scale, Samples = new List(RepeatSamples[repeatIndex]) diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 9c9fc2e742..85efdca07b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> repeatSamples) { newCombo |= forceNewCombo; comboOffset += extraComboOffset; @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch ComboOffset = comboOffset, ControlPoints = controlPoints, Distance = length, - CurveType = curveType, + PathType = pathType, RepeatSamples = repeatSamples, RepeatCount = repeatCount }; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 965e76d27a..73f70d414f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } else if (type.HasFlag(ConvertHitObjectType.Slider)) { - CurveType curveType = CurveType.Catmull; + PathType pathType = PathType.Catmull; double length = 0; string[] pointSplit = split[5].Split('|'); @@ -90,16 +90,16 @@ namespace osu.Game.Rulesets.Objects.Legacy switch (t) { case @"C": - curveType = CurveType.Catmull; + pathType = PathType.Catmull; break; case @"B": - curveType = CurveType.Bezier; + pathType = PathType.Bezier; break; case @"L": - curveType = CurveType.Linear; + pathType = PathType.Linear; break; case @"P": - curveType = CurveType.PerfectCurve; + pathType = PathType.PerfectCurve; break; } @@ -113,8 +113,8 @@ namespace osu.Game.Rulesets.Objects.Legacy // osu-stable special-cased colinear perfect curves to a CurveType.Linear bool isLinear(Vector2[] p) => Precision.AlmostEquals(0, (p[1].Y - p[0].Y) * (p[2].X - p[0].X) - (p[1].X - p[0].X) * (p[2].Y - p[0].Y)); - if (points.Length == 3 && curveType == CurveType.PerfectCurve && isLinear(points)) - curveType = CurveType.Linear; + if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points)) + pathType = PathType.Linear; int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture); @@ -178,7 +178,7 @@ namespace osu.Game.Rulesets.Objects.Legacy for (int i = 0; i < nodes; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); - result = CreateSlider(pos, combo, comboOffset, points, length, curveType, repeatCount, nodeSamples); + result = CreateSlider(pos, combo, comboOffset, points, length, pathType, repeatCount, nodeSamples); } else if (type.HasFlag(ConvertHitObjectType.Spinner)) { @@ -268,11 +268,11 @@ namespace osu.Game.Rulesets.Objects.Legacy /// When starting a new combo, the offset of the new combo relative to the current one. /// The slider control points. /// The slider length. - /// The slider curve type. + /// The slider curve type. /// The slider repeat count. /// The samples to be played when the repeat nodes are hit. This includes the head and tail of the slider. /// The hit object. - protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples); + protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> repeatSamples); /// /// Creates a legacy Spinner-type hit object. diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 93c49ea3ce..6030bff427 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -20,9 +20,9 @@ namespace osu.Game.Rulesets.Objects.Legacy /// /// s don't need a curve since they're converted to ruleset-specific hitobjects. /// - public SliderCurve Curve { get; } = null; + public SliderPath Path { get; } = null; public Vector2[] ControlPoints { get; set; } - public CurveType CurveType { get; set; } + public PathType PathType { get; set; } public double Distance { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index 68e05f6223..6f10880aa2 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -26,14 +26,14 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> repeatSamples) { return new ConvertSlider { X = position.X, ControlPoints = controlPoints, Distance = length, - CurveType = curveType, + PathType = pathType, RepeatSamples = repeatSamples, RepeatCount = repeatCount }; diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index f3c815fc32..31c200eddc 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> repeatSamples) { newCombo |= forceNewCombo; comboOffset += extraComboOffset; @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu ComboOffset = comboOffset, ControlPoints = controlPoints, Distance = Math.Max(0, length), - CurveType = curveType, + PathType = pathType, RepeatSamples = repeatSamples, RepeatCount = repeatCount }; diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index 985a032640..0a244bb6c6 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -23,13 +23,13 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko return new ConvertHit(); } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, CurveType curveType, int repeatCount, List> repeatSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> repeatSamples) { return new ConvertSlider { ControlPoints = controlPoints, Distance = length, - CurveType = curveType, + PathType = pathType, RepeatSamples = repeatSamples, RepeatCount = repeatCount }; diff --git a/osu.Game/Rulesets/Objects/SliderCurve.cs b/osu.Game/Rulesets/Objects/SliderPath.cs similarity index 84% rename from osu.Game/Rulesets/Objects/SliderCurve.cs rename to osu.Game/Rulesets/Objects/SliderPath.cs index dfccdf68f2..46f8cae8a0 100644 --- a/osu.Game/Rulesets/Objects/SliderCurve.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -10,13 +10,13 @@ using OpenTK; namespace osu.Game.Rulesets.Objects { - public class SliderCurve + public class SliderPath { public double Distance; public Vector2[] ControlPoints; - public CurveType CurveType = CurveType.PerfectCurve; + public PathType PathType = PathType.PerfectCurve; public Vector2 Offset; @@ -25,15 +25,15 @@ namespace osu.Game.Rulesets.Objects private List calculateSubpath(ReadOnlySpan subControlPoints) { - switch (CurveType) + switch (PathType) { - case CurveType.Linear: + case PathType.Linear: var result = new List(subControlPoints.Length); foreach (var c in subControlPoints) result.Add(c); return result; - case CurveType.PerfectCurve: + case PathType.PerfectCurve: //we can only use CircularArc iff we have exactly three control points and no dissection. if (ControlPoints.Length != 3 || subControlPoints.Length != 3) break; @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Objects break; return subpath; - case CurveType.Catmull: + case PathType.Catmull: return new CatmullApproximator(subControlPoints).CreateCatmull(); } @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.Objects Vector2 diff = calculatedPath[i + 1] - calculatedPath[i]; double d = diff.Length; - // Shorten slider curves that are too long compared to what's + // Shorten slider paths that are too long compared to what's // in the .osu file. if (Distance - l < d) { @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Objects cumulativeLength.Add(l); } - // Lengthen slider curves that are too short compared to what's + // Lengthen slider paths that are too short compared to what's // in the .osu file. if (l < Distance && calculatedPath.Count > 1) { @@ -124,10 +124,33 @@ namespace osu.Game.Rulesets.Objects } } - public void Calculate() + private void calculateCumulativeLength() + { + 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; + + l += d; + cumulativeLength.Add(l); + } + + Distance = l; + } + + public void Calculate(bool updateDistance = false) { calculatePath(); - calculateCumulativeLengthAndTrimPath(); + + if (!updateDistance) + calculateCumulativeLengthAndTrimPath(); + else + calculateCumulativeLength(); } private int indexOfDistance(double d) @@ -168,10 +191,10 @@ namespace osu.Game.Rulesets.Objects } /// - /// Computes the slider curve until a given progress that ranges from 0 (beginning of the slider) + /// 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. /// - /// The list to be filled with the computed curve. + /// The list to be filled with the computed path. /// Start progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). /// End progress. Ranges from 0 (beginning of the slider) to 1 (end of the slider). public void GetPathToProgress(List path, double p0, double p1) @@ -196,10 +219,10 @@ namespace osu.Game.Rulesets.Objects } /// - /// Computes the position on the slider at a given progress that ranges from 0 (beginning of the curve) - /// to 1 (end of the curve). + /// Computes the position on the slider at a given progress that ranges from 0 (beginning of the path) + /// to 1 (end of the path). /// - /// Ranges from 0 (beginning of the curve) to 1 (end of the curve). + /// Ranges from 0 (beginning of the path) to 1 (end of the path). /// public Vector2 PositionAt(double progress) { diff --git a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs index 69b2f722e7..2a0d495e94 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The curve. /// - SliderCurve Curve { get; } + SliderPath Path { get; } /// /// The control points that shape the curve. @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The type of curve. /// - CurveType CurveType { get; } + PathType PathType { get; } } public static class HasCurveExtensions @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Objects.Types /// [0, 1] where 0 is the start time of the and 1 is the end time of the . /// The position on the curve. public static Vector2 CurvePositionAt(this IHasCurve obj, double progress) - => obj.Curve.PositionAt(obj.ProgressAt(progress)); + => obj.Path.PositionAt(obj.ProgressAt(progress)); /// /// Computes the progress along the curve relative to how much of the has been completed. diff --git a/osu.Game/Rulesets/Objects/Types/CurveType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs similarity index 91% rename from osu.Game/Rulesets/Objects/Types/CurveType.cs rename to osu.Game/Rulesets/Objects/Types/PathType.cs index 1cee6202b6..5156302fe1 100644 --- a/osu.Game/Rulesets/Objects/Types/CurveType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -3,7 +3,7 @@ namespace osu.Game.Rulesets.Objects.Types { - public enum CurveType + public enum PathType { Catmull, Bezier,