diff --git a/osu-framework b/osu-framework index 7ca1719b5c..7b2f4dfce7 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 7ca1719b5cdc8b0a9600abe6472b38a426abedb0 +Subproject commit 7b2f4dfce7894ca7dea7626dcc34bcc32df651b9 diff --git a/osu.Game.Mode.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Mode.Osu/Objects/Drawables/DrawableSlider.cs index 8882049cbb..cd907fc001 100644 --- a/osu.Game.Mode.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Mode.Osu/Objects/Drawables/DrawableSlider.cs @@ -1,8 +1,10 @@ -using System.Collections.Generic; +// Copyright (c) 2007-2016 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE + +using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Modes.Objects.Drawables; -using osu.Game.Modes.Osu.Objects.Drawables.Pieces; using OpenTK; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Transformations; @@ -11,6 +13,9 @@ using osu.Framework.Input; using OpenTK.Graphics.ES30; using osu.Framework.Allocation; using osu.Framework.Graphics.Textures; +using osu.Game.Configuration; +using osu.Framework.Configuration; +using System; namespace osu.Game.Modes.Osu.Objects.Drawables { @@ -49,30 +54,77 @@ namespace osu.Game.Modes.Osu.Objects.Drawables }; } + private Bindable snakingIn; + private Bindable snakingOut; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + snakingIn = config.GetBindable(OsuConfig.SnakingInSliders); + snakingOut = config.GetBindable(OsuConfig.SnakingOutSliders); + } + protected override void LoadComplete() { base.LoadComplete(); //force application of the state that was set before we loaded. UpdateState(State); + + body.PathWidth = 32; + } + + private void computeProgress(out int repeat, out double progress) + { + progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); + + repeat = (int)(progress * slider.RepeatCount); + progress = (progress * slider.RepeatCount) % 1; + + if (repeat % 2 == 1) + progress = 1 - progress; + } + + private void updateBall(double progress) + { + ball.Alpha = Time.Current >= slider.StartTime && Time.Current <= slider.EndTime ? 1 : 0; + ball.Position = slider.Curve.PositionAt(progress); + } + + private void updateBody(int repeat, double progress) + { + double drawStartProgress = 0; + double drawEndProgress = MathHelper.Clamp((Time.Current - slider.StartTime + TIME_PREEMPT) / TIME_FADEIN, 0, 1); + + if (repeat >= slider.RepeatCount - 1) + { + if (Math.Min(repeat, slider.RepeatCount - 1) % 2 == 1) + { + drawStartProgress = 0; + drawEndProgress = progress; + } + else + { + drawStartProgress = progress; + drawEndProgress = 1; + } + } + + body.SetRange( + snakingOut ? drawStartProgress : 0, + snakingIn ? drawEndProgress : 1); } protected override void Update() { base.Update(); - ball.Alpha = Time.Current >= slider.StartTime && Time.Current <= slider.EndTime ? 1 : 0; + double progress; + int repeat; + computeProgress(out repeat, out progress); - double t = (Time.Current - slider.StartTime) / slider.Duration; - if (slider.RepeatCount > 1) - { - int currentRepeat = (int)(t * slider.RepeatCount); - t = (t * slider.RepeatCount) % 1; - if (currentRepeat % 2 == 1) - t = 1 - t; - } - - ball.Position = slider.Curve.PositionAt(t); + updateBall(progress); + updateBody(repeat, progress); } protected override void CheckJudgement(bool userTriggered) @@ -187,7 +239,14 @@ namespace osu.Game.Modes.Osu.Objects.Drawables private Path path; private BufferedContainer container; - private double? drawnProgress; + public float PathWidth + { + get { return path.PathWidth; } + set { path.PathWidth = value; } + } + + private double? drawnProgressStart; + private double? drawnProgressEnd; private Slider slider; public Body(Slider s) @@ -221,69 +280,38 @@ namespace osu.Game.Modes.Osu.Objects.Drawables path.Texture = textures.Get(@"Menu/logo"); } - protected override void LoadComplete() + public void SetRange(double p0, double p1) { - base.LoadComplete(); - path.PathWidth = 32; - } + if (p0 > p1) + MathHelper.Swap(ref p0, ref p1); - protected override void Update() - { - base.Update(); - - if (updateSnaking()) + 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.HeadPosition; + container.Position = -path.PositionInBoundingBox(slider.Curve.PositionAt(0) - currentCurve[0]); container.ForceRedraw(); } } - private bool updateSnaking() + private List currentCurve = new List(); + private bool updateSnaking(double p0, double p1) { - double progress = MathHelper.Clamp((Time.Current - slider.StartTime + TIME_PREEMPT) / TIME_FADEIN, 0, 1); + if (drawnProgressStart == p0 && drawnProgressEnd == p1) return false; - if (progress == drawnProgress) return false; + drawnProgressStart = p0; + drawnProgressEnd = p1; - bool madeChanges = false; - if (progress == 0) - { - //if we have gone backwards, just clear the path for now. - drawnProgress = 0; - path.ClearVertices(); - madeChanges = true; - } + slider.Curve.GetPathToProgress(currentCurve, p0, p1); - Vector2 startPosition = slider.Curve.PositionAt(0); + path.ClearVertices(); + foreach (Vector2 p in currentCurve) + path.AddVertex(p - currentCurve[0]); - if (drawnProgress == null) - { - drawnProgress = 0; - path.AddVertex(slider.Curve.PositionAt(drawnProgress.Value) - startPosition); - madeChanges = true; - } - - double segmentSize = 1 / (slider.Curve.Length / 5); - - while (drawnProgress + segmentSize < progress) - { - drawnProgress += segmentSize; - path.AddVertex(slider.Curve.PositionAt(drawnProgress.Value) - startPosition); - madeChanges = true; - } - - if (progress == 1 && drawnProgress != progress) - { - drawnProgress = progress; - path.AddVertex(slider.Curve.PositionAt(drawnProgress.Value) - startPosition); - madeChanges = true; - } - - return madeChanges; + return true; } } } diff --git a/osu.Game.Mode.Osu/Objects/SliderCurve.cs b/osu.Game.Mode.Osu/Objects/SliderCurve.cs index 4e691196f5..d80a441daf 100644 --- a/osu.Game.Mode.Osu/Objects/SliderCurve.cs +++ b/osu.Game.Mode.Osu/Objects/SliderCurve.cs @@ -1,5 +1,12 @@ -using System.Collections.Generic; +// Copyright (c) 2007-2016 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE + +using System.Collections.Generic; using OpenTK; +using System.Linq; +using System.Diagnostics; +using osu.Framework.MathUtils; +using System; namespace osu.Game.Modes.Osu.Objects { @@ -11,7 +18,8 @@ namespace osu.Game.Modes.Osu.Objects public CurveTypes CurveType; - private List calculatedPath; + private List calculatedPath = new List(); + private List cumulativeLength = new List(); private List calculateSubpath(List subpath) { @@ -24,9 +32,9 @@ namespace osu.Game.Modes.Osu.Objects } } - public void Calculate() + private void calculatePath() { - calculatedPath = new List(); + calculatedPath.Clear(); // Sliders may consist of various subpaths separated by two consecutive vertices // with the same position. The following loop parses these subpaths and computes @@ -50,18 +58,126 @@ namespace osu.Game.Modes.Osu.Objects } } + private void calculateCumulativeLengthAndTrimPath() + { + 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; + + // Shorten slider curves that are too long compared to what's + // in the .osu file. + if (Length - l < d) + { + calculatedPath[i + 1] = calculatedPath[i] + diff * (float)((Length - l) / d); + calculatedPath.RemoveRange(i + 2, calculatedPath.Count - 2 - i); + + l = Length; + cumulativeLength.Add(l); + break; + } + + l += d; + cumulativeLength.Add(l); + } + + // Lengthen slider curves that are too short compared to what's + // in the .osu file. + if (l < Length && calculatedPath.Count > 1) + { + Vector2 diff = calculatedPath[calculatedPath.Count - 1] - calculatedPath[calculatedPath.Count - 2]; + double d = diff.Length; + + if (d <= 0) + return; + + calculatedPath[calculatedPath.Count - 1] += diff * (float)((Length - l) / d); + cumulativeLength[calculatedPath.Count - 1] = Length; + } + } + + public void Calculate() + { + calculatePath(); + calculateCumulativeLengthAndTrimPath(); + } + + private int indexOfDistance(double d) + { + int i = cumulativeLength.BinarySearch(d); + if (i < 0) i = ~i; + + return i; + } + + private double progressToDistance(double progress) + { + return MathHelper.Clamp(progress, 0, 1) * Length; + } + + private Vector2 interpolateVertices(int i, double d) + { + if (calculatedPath.Count == 0) + return Vector2.Zero; + + if (i <= 0) + return calculatedPath.First(); + else if (i >= calculatedPath.Count) + return calculatedPath.Last(); + + Vector2 p0 = calculatedPath[i - 1]; + Vector2 p1 = calculatedPath[i]; + + double d0 = cumulativeLength[i - 1]; + double d1 = cumulativeLength[i]; + + // Avoid division by and almost-zero number in case two points are extremely close to each other. + if (Precision.AlmostEquals(d0, d1)) + return p0; + + double w = (d - d0) / (d1 - d0); + return p0 + (p1 - p0) * (float)w; + } + + /// + /// Computes the slider curve 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. + /// Ranges from 0 (beginning of the slider) to 1 (end of the slider). + public void GetPathToProgress(List path, double p0, double p1) + { + double d0 = progressToDistance(p0); + double d1 = progressToDistance(p1); + + path.Clear(); + + int i = 0; + for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i); + + path.Add(interpolateVertices(i, d0)); + + for (; i < calculatedPath.Count && cumulativeLength[i] <= d1; ++i) + path.Add(calculatedPath[i]); + + path.Add(interpolateVertices(i, d1)); + } + + /// + /// Computes the position on the slider at a given progress that ranges from 0 (beginning of the slider) + /// to 1 (end of the slider). + /// + /// Ranges from 0 (beginning of the slider) to 1 (end of the slider). + /// public Vector2 PositionAt(double progress) { - progress = MathHelper.Clamp(progress, 0, 1); - - double index = progress * (calculatedPath.Count - 1); - int flooredIndex = (int)index; - - Vector2 pos = calculatedPath[flooredIndex]; - if (index != flooredIndex) - pos += (calculatedPath[flooredIndex + 1] - pos) * (float)(index - flooredIndex); - - return pos; + double d = progressToDistance(progress); + return interpolateVertices(indexOfDistance(d), d); } } } \ No newline at end of file diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 3f8ae0e33a..fcdef1ce39 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -33,7 +33,7 @@ namespace osu.Game.Beatmaps double mult = 1; - if (applyMultipliers && samplePoint > point && ControlPoints[samplePoint].BeatLength < 0) + if (applyMultipliers && samplePoint > point) mult = ControlPoints[samplePoint].VelocityAdjustment; return ControlPoints[point].BeatLength * mult; diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 8657f74d87..8e5fbfad42 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -4,12 +4,10 @@ using osu.Framework.Configuration; using osu.Framework.Platform; using osu.Game.Modes; -using osu.Game.Online.API; -using osu.Game.Screens.Play; namespace osu.Game.Configuration { - class OsuConfigManager : ConfigManager + public class OsuConfigManager : ConfigManager { protected override void InitialiseDefaults() { @@ -129,7 +127,8 @@ namespace osu.Game.Configuration //Set(OsuConfig.Skin, SkinManager.DEFAULT_SKIN); Set(OsuConfig.SkinSamples, true); Set(OsuConfig.SkipTablet, false); - Set(OsuConfig.SnakingSliders, true); + Set(OsuConfig.SnakingInSliders, true); + Set(OsuConfig.SnakingOutSliders, false); Set(OsuConfig.Tablet, false); Set(OsuConfig.UpdatePending, false); Set(OsuConfig.UseSkinCursor, false); @@ -189,7 +188,7 @@ namespace osu.Game.Configuration } } - enum OsuConfig + public enum OsuConfig { // New osu: PlayMode, @@ -303,7 +302,8 @@ namespace osu.Game.Configuration Skin, SkinSamples, SkipTablet, - SnakingSliders, + SnakingInSliders, + SnakingOutSliders, Tablet, UpdatePending, UserFilter, @@ -345,5 +345,6 @@ namespace osu.Game.Configuration Ticker, CompatibilityContext, CanForceOptimusCompatibility, + } } diff --git a/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs index 8e41fb6521..ed57f06e51 100644 --- a/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Modes/Objects/Drawables/DrawableHitObject.cs @@ -87,9 +87,9 @@ namespace osu.Game.Modes.Objects.Drawables //todo: consider making abstract. } - protected override void Update() + protected override void UpdateAfterChildren() { - base.Update(); + base.UpdateAfterChildren(); UpdateJudgement(false); } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 5b35b1e672..4db582d799 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -1,12 +1,12 @@ -using System; +// Copyright (c) 2007-2016 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE + using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Configuration; -using osu.Framework.GameModes; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; @@ -15,11 +15,7 @@ using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Processing; -using osu.Game.IPC; -using osu.Game.Online; using osu.Game.Online.API; -using osu.Game.Overlays; -using osu.Game.Online.API.Requests; namespace osu.Game { diff --git a/osu.Game/Overlays/Options/Graphics/DetailOptions.cs b/osu.Game/Overlays/Options/Graphics/DetailOptions.cs index 12df61f52b..880677ff6a 100644 --- a/osu.Game/Overlays/Options/Graphics/DetailOptions.cs +++ b/osu.Game/Overlays/Options/Graphics/DetailOptions.cs @@ -1,8 +1,9 @@ -using osu.Framework; +// Copyright (c) 2007-2016 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE + using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; using osu.Game.Configuration; namespace osu.Game.Overlays.Options.Graphics @@ -18,8 +19,13 @@ namespace osu.Game.Overlays.Options.Graphics { new CheckBoxOption { - LabelText = "Snaking sliders", - Bindable = config.GetBindable(OsuConfig.SnakingSliders) + LabelText = "Snaking in sliders", + Bindable = config.GetBindable(OsuConfig.SnakingInSliders) + }, + new CheckBoxOption + { + LabelText = "Snaking out sliders", + Bindable = config.GetBindable(OsuConfig.SnakingOutSliders) }, new CheckBoxOption { diff --git a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs index bc60123c02..22c214d82f 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarModeSelector.cs @@ -106,9 +106,9 @@ namespace osu.Game.Overlays.Toolbar private Cached activeMode = new Cached(); - protected override void UpdateLayout() + protected override void UpdateAfterChildren() { - base.UpdateLayout(); + base.UpdateAfterChildren(); if (!activeMode.EnsureValid()) activeMode.Refresh(() => modeButtonLine.MoveToX(activeButton.DrawPosition.X, 200, EasingTypes.OutQuint));