diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index db704b0553..3e1b64bb86 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables List curve = drawableSlider.Body.CurrentCurve; var positionOnCurve = isRepeatAtEnd ? end : start; - Position = positionOnCurve + drawableSlider.HitObject.StackOffset; + Position = positionOnCurve - curve[0] + drawableSlider.HitObject.StackOffset; if (curve.Count < 2) return; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 391e0ff023..866631468a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -30,6 +30,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { slider = s; + Position = s.StackedPosition; + DrawableSliderTail tail; Container ticks; Container repeatPoints; @@ -39,20 +41,20 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Body = new SliderBody(s) { AccentColour = AccentColour, - Position = s.StackedPosition, PathWidth = s.Scale * 64, }, - ticks = new Container(), - repeatPoints = new Container(), + ticks = new Container { RelativeSizeAxes = Axes.Both }, + repeatPoints = new Container { RelativeSizeAxes = Axes.Both }, Ball = new SliderBall(s) { + BypassAutoSizeAxes = Axes.Both, Scale = new Vector2(s.Scale), AccentColour = AccentColour, AlwaysPresent = true, Alpha = 0 }, - HeadCircle = new DrawableHitCircle(s.HeadCircle), - tail = new DrawableSliderTail(s.TailCircle) + HeadCircle = new DrawableHitCircle(s.HeadCircle) { Position = s.HeadCircle.StackedPosition }, + tail = new DrawableSliderTail(s.TailCircle) { Position = s.TailCircle.StackedPosition } }; components.Add(Body); @@ -112,6 +114,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables 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 t in components.OfType()) t.Tracking = Ball.Tracking; + + Size = Body.Size; + OriginPosition = Body.PathOffset; + + foreach (var obj in NestedHitObjects) + obj.RelativeAnchorPosition = Vector2.Divide(OriginPosition, Body.DrawSize); + Ball.RelativeAnchorPosition = Vector2.Divide(OriginPosition, Body.DrawSize); } protected override void CheckForJudgements(bool userTriggered, double timeOffset) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 8835fc2b29..b907aea8c3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -19,8 +19,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public DrawableSliderTail(HitCircle hitCircle) : base(hitCircle) { - AlwaysPresent = true; + Origin = Anchor.Centre; + RelativeSizeAxes = Axes.Both; + FillMode = FillMode.Fit; + + AlwaysPresent = true; } protected override void CheckForJudgements(bool userTriggered, double timeOffset) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index a83ee3a2e1..8c0eb7ff7d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -29,6 +29,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces set { path.PathWidth = value; } } + /// + /// Offset in absolute coordinates from the start of the curve. + /// + public Vector2 PathOffset { get; private set; } + + public readonly List CurrentCurve = new List(); + public readonly Bindable SnakingIn = new Bindable(); public readonly Bindable SnakingOut = new Bindable(); @@ -75,6 +82,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private int textureWidth => (int)PathWidth * 2; + private Vector2 topLeftOffset; + private readonly Slider slider; public SliderBody(Slider s) { @@ -84,6 +93,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { container = new BufferedContainer { + RelativeSizeAxes = Axes.Both, CacheDrawnFrameBuffer = true, Children = new Drawable[] { @@ -107,11 +117,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces if (updateSnaking(p0, p1)) { - // Autosizing does not give us the desired behaviour here. - // We want the container to have the same size as the slider, - // and to be positioned such that the slider head is at (0,0). - container.Size = path.Size; - container.Position = -path.PositionInBoundingBox(slider.Curve.PositionAt(0) - CurrentCurve[0]); + // The path is generated such that its size encloses it. This change of size causes the path + // to move around while snaking, so we need to offset it to make sure it maintains the + // same position as when it is fully snaked. + var newTopLeftOffset = path.PositionInBoundingBox(Vector2.Zero); + path.Position = topLeftOffset - newTopLeftOffset; container.ForceRedraw(); } @@ -121,6 +131,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private void load() { reloadTexture(); + computeSize(); } private void reloadTexture() @@ -164,7 +175,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces path.Texture = texture; } - public readonly List CurrentCurve = new List(); + private void computeSize() + { + // Generate the entire curve + slider.Curve.GetPathToProgress(CurrentCurve, 0, 1); + foreach (Vector2 p in CurrentCurve) + path.AddVertex(p); + + Size = path.Size; + + topLeftOffset = path.PositionInBoundingBox(Vector2.Zero); + PathOffset = path.PositionInBoundingBox(CurrentCurve[0]); + } + private bool updateSnaking(double p0, double p1) { if (SnakedStart == p0 && SnakedEnd == p1) return false; @@ -176,7 +199,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces path.ClearVertices(); foreach (Vector2 p in CurrentCurve) - path.AddVertex(p - CurrentCurve[0]); + path.AddVertex(p); return true; } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index ce6c88a340..4905972e6f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Objects HeadCircle = new HitCircle { StartTime = StartTime, - Position = StackedPosition, + Position = this.PositionAt(0), IndexInCurrentCombo = IndexInCurrentCombo, ComboColour = ComboColour, Samples = Samples, @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Osu.Objects TailCircle = new HitCircle { StartTime = EndTime, - Position = StackedEndPosition, + Position = this.PositionAt(1), IndexInCurrentCombo = IndexInCurrentCombo, ComboColour = ComboColour }; @@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Osu.Objects SpanIndex = span, SpanStartTime = spanStartTime, StartTime = spanStartTime + timeProgress * SpanDuration, - Position = Curve.PositionAt(distanceProgress), + Position = Curve.PositionAt(distanceProgress) - Curve.PositionAt(0), StackHeight = StackHeight, Scale = Scale, ComboColour = ComboColour, @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Osu.Objects RepeatIndex = repeatIndex, SpanDuration = SpanDuration, StartTime = StartTime + repeat * SpanDuration, - Position = Curve.PositionAt(repeat % 2), + Position = Curve.PositionAt(repeat % 2) - Curve.PositionAt(0), StackHeight = StackHeight, Scale = Scale, ComboColour = ComboColour, @@ -184,4 +184,10 @@ namespace osu.Game.Rulesets.Osu.Objects } } } + + public static class SliderExtensions + { + public static Vector2 PositionAt(this Slider slider, double progress) + => ((IHasCurve)slider).PositionAt(progress) - slider.Curve.PositionAt(0); + } } diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseNewSliderBody.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseNewSliderBody.cs new file mode 100644 index 0000000000..a23bfb11ca --- /dev/null +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseNewSliderBody.cs @@ -0,0 +1,171 @@ +// 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.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Lines; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Tests.Visual; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestCaseNewSliderBody : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(Path) }; + + private readonly NewSliderBody body; + + public TestCaseNewSliderBody() + { + Add(body = new NewSliderBody(new SliderCurve + { + ControlPoints = new List + { + new Vector2(-200, 0), + new Vector2(-50, 75), + new Vector2(0, 100), + new Vector2(100, -200), + new Vector2(230, 0) + }, + Distance = 480, + CurveType = CurveType.Bezier + }) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + + AddSliderStep("In", 0f, 1f, 0f, v => inLength = v); + AddSliderStep("Out", 0f, 1f, 1f, v => outLength = v); + AddSliderStep("Path Width", 0f, 100f, 10f, v => body.PathWidth = v); + } + + private float _inLength; + + private float inLength + { + set + { + _inLength = value; + body.UpdateSnaking(_inLength, _outLength); + } + } + + private float _outLength; + + private float outLength + { + set + { + _outLength = value; + body.UpdateSnaking(_inLength, _outLength); + } + } + + private class NewSliderBody : CompositeDrawable + { + private readonly Path path; + private readonly SliderCurve curve; + + public NewSliderBody(SliderCurve curve) + { + this.curve = curve; + + InternalChild = path = new Path(); + } + + [BackgroundDependencyLoader] + private void load() + { + reloadTexture(); + computeSize(); + } + + public float PathWidth + { + get => path.PathWidth; + set { path.PathWidth = value; reloadTexture(); } + } + + private void reloadTexture() + { + var textureWidth = (int)PathWidth * 2; + + //initialise background + var texture = new Texture(textureWidth, 1); + var upload = new TextureUpload(textureWidth * 4); + var bytes = upload.Data; + + const float aa_portion = 0.02f; + const float border_portion = 0.128f; + const float gradient_portion = 1 - border_portion; + + const float opacity_at_centre = 0.3f; + const float opacity_at_edge = 0.8f; + + for (int i = 0; i < textureWidth; i++) + { + float progress = (float)i / (textureWidth - 1); + + if (progress <= border_portion) + { + bytes[i * 4] = (byte)(Color4.White.R * 255); + bytes[i * 4 + 1] = (byte)(Color4.White.G * 255); + bytes[i * 4 + 2] = (byte)(Color4.White.B * 255); + bytes[i * 4 + 3] = (byte)(Math.Min(progress / aa_portion, 1) * (Color4.White.A * 255)); + } + else + { + progress -= border_portion; + + bytes[i * 4] = (byte)(Color4.Blue.R * 255); + bytes[i * 4 + 1] = (byte)(Color4.Blue.G * 255); + bytes[i * 4 + 2] = (byte)(Color4.Blue.B * 255); + bytes[i * 4 + 3] = (byte)((opacity_at_edge - (opacity_at_edge - opacity_at_centre) * progress / gradient_portion) * (Color4.Blue.A * 255)); + } + } + + texture.SetData(upload); + path.Texture = texture; + } + + private Vector2 topLeftOffset; + + private void computeSize() + { + // Compute the final size + var fullPath = new List(); + curve.GetPathToProgress(fullPath, 0, 1); + + foreach (Vector2 p in fullPath) + path.AddVertex(p); + + Size = path.Size; + + topLeftOffset = path.PositionInBoundingBox(Vector2.Zero); + OriginPosition = path.PositionInBoundingBox(fullPath[0]); + } + + public void UpdateSnaking(float t0, float t1) + { + var curvePath = new List(); + curve.GetPathToProgress(curvePath, t0, t1); + + path.ClearVertices(); + foreach (Vector2 p in curvePath) + path.AddVertex(p); + + var newTopLeftOffset = path.PositionInBoundingBox(Vector2.Zero); + path.Position = topLeftOffset - newTopLeftOffset; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 7838fb7707..53075728ad 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -124,6 +124,7 @@ +