diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 641c8066a5..3844a5903c 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -30,19 +30,30 @@ namespace osu.Game.Rulesets.UI.Scrolling protected readonly SortedList ControlPoints = new SortedList(); public readonly Bindable Direction = new Bindable(); - private readonly SpeedChangeVisualisationMethod visualisationMethod; + + private readonly ISpeedChangeVisualiser visualiser; private Cached initialStateCache = new Cached(); - private ISpeedChangeVisualiser visualiser; public ScrollingHitObjectContainer(SpeedChangeVisualisationMethod visualisationMethod) { - this.visualisationMethod = visualisationMethod; - RelativeSizeAxes = Axes.Both; TimeRange.ValueChanged += _ => initialStateCache.Invalidate(); Direction.ValueChanged += _ => initialStateCache.Invalidate(); + + switch (visualisationMethod) + { + case SpeedChangeVisualisationMethod.Sequential: + visualiser = new SequentialSpeedChangeVisualiser(ControlPoints); + break; + case SpeedChangeVisualisationMethod.Overlapping: + visualiser = new OverlappingSpeedChangeVisualiser(ControlPoints); + break; + case SpeedChangeVisualisationMethod.Constant: + visualiser = new ConstantSpeedChangeVisualiser(); + break; + } } public override void Add(DrawableHitObject hitObject) @@ -81,13 +92,26 @@ namespace osu.Game.Rulesets.UI.Scrolling return base.Invalidate(invalidation, source, shallPropagate); } + private float scrollLength; + protected override void Update() { base.Update(); if (!initialStateCache.IsValid) { - visualiser = createVisualiser(); + switch (Direction.Value) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + scrollLength = DrawSize.Y; + break; + default: + scrollLength = DrawSize.X; + break; + } + + visualiser.Reset(); foreach (var obj in Objects) computeInitialStateRecursive(obj); @@ -95,36 +119,9 @@ namespace osu.Game.Rulesets.UI.Scrolling } } - private ISpeedChangeVisualiser createVisualiser() - { - float scrollLength; - - switch (Direction.Value) - { - case ScrollingDirection.Up: - case ScrollingDirection.Down: - scrollLength = DrawSize.Y; - break; - default: - scrollLength = DrawSize.X; - break; - } - - switch (visualisationMethod) - { - default: - case SpeedChangeVisualisationMethod.Constant: - return new ConstantSpeedChangeVisualiser(TimeRange, scrollLength); - case SpeedChangeVisualisationMethod.Overlapping: - return new OverlappingSpeedChangeVisualiser(ControlPoints, TimeRange, scrollLength); - case SpeedChangeVisualisationMethod.Sequential: - return new SequentialSpeedChangeVisualiser(ControlPoints, TimeRange, scrollLength); - } - } - private void computeInitialStateRecursive(DrawableHitObject hitObject) { - hitObject.LifetimeStart = visualiser.GetDisplayStartTime(hitObject.HitObject.StartTime); + hitObject.LifetimeStart = visualiser.GetDisplayStartTime(hitObject.HitObject.StartTime, TimeRange); if (hitObject.HitObject is IHasEndTime endTime) { @@ -132,11 +129,11 @@ namespace osu.Game.Rulesets.UI.Scrolling { case ScrollingDirection.Up: case ScrollingDirection.Down: - hitObject.Height = visualiser.GetLength(hitObject.HitObject.StartTime, endTime.EndTime); + hitObject.Height = visualiser.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, TimeRange, scrollLength); break; case ScrollingDirection.Left: case ScrollingDirection.Right: - hitObject.Height = visualiser.GetLength(hitObject.HitObject.StartTime, endTime.EndTime); + hitObject.Height = visualiser.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, TimeRange, scrollLength); break; } } @@ -164,16 +161,16 @@ namespace osu.Game.Rulesets.UI.Scrolling switch (Direction.Value) { case ScrollingDirection.Up: - hitObject.Y = visualiser.PositionAt(hitObject.HitObject.StartTime, currentTime); + hitObject.Y = visualiser.PositionAt(hitObject.HitObject.StartTime, currentTime, TimeRange, scrollLength); break; case ScrollingDirection.Down: - hitObject.Y = -visualiser.PositionAt(hitObject.HitObject.StartTime, currentTime); + hitObject.Y = -visualiser.PositionAt(hitObject.HitObject.StartTime, currentTime, TimeRange, scrollLength); break; case ScrollingDirection.Left: - hitObject.X = visualiser.PositionAt(hitObject.HitObject.StartTime, currentTime); + hitObject.X = visualiser.PositionAt(hitObject.HitObject.StartTime, currentTime, TimeRange, scrollLength); break; case ScrollingDirection.Right: - hitObject.X = -visualiser.PositionAt(hitObject.HitObject.StartTime, currentTime); + hitObject.X = -visualiser.PositionAt(hitObject.HitObject.StartTime, currentTime, TimeRange, scrollLength); break; } } diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ConstantSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ConstantSpeedChangeVisualiser.cs index 7c8f9018a9..09710392c9 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ConstantSpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ConstantSpeedChangeVisualiser.cs @@ -3,26 +3,22 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers { - public readonly struct ConstantSpeedChangeVisualiser : ISpeedChangeVisualiser + public class ConstantSpeedChangeVisualiser : ISpeedChangeVisualiser { - private readonly double timeRange; - private readonly float scrollLength; + public double GetDisplayStartTime(double time, double timeRange) => time - timeRange; - public ConstantSpeedChangeVisualiser(double timeRange, float scrollLength) - { - this.timeRange = timeRange; - this.scrollLength = scrollLength; - } - - public double GetDisplayStartTime(double time) => time - timeRange; - - public float GetLength(double startTime, double endTime) + public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) { // At the hitobject's end time, the hitobject will be positioned such that its end rests at the origin. // This results in a negative-position value, and the absolute of it indicates the length of the hitobject. - return -PositionAt(startTime, endTime); + return -PositionAt(startTime, endTime, timeRange, scrollLength); } - public float PositionAt(double time, double currentTime) => (float)((time - currentTime) / timeRange * scrollLength); + public float PositionAt(double time, double currentTime, double timeRange, float scrollLength) + => (float)((time - currentTime) / timeRange * scrollLength); + + public void Reset() + { + } } } diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs index b4435f558d..f950e7f375 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/ISpeedChangeVisualiser.cs @@ -6,29 +6,39 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers public interface ISpeedChangeVisualiser { /// - /// Given a point in time, computes the time at which the point enters the visible time range of this . + /// Given a point in time, computes the time at which it enters the time range. /// /// - /// E.g. For a constant visible time range of 5000ms, the time at which t=7000ms enters the visible time range is 2000ms. + /// E.g. For a constant time range of 5000ms, the time at which t=7000ms enters the time range is 2000ms. /// - /// The time value. - /// The time at which enters the visible time range of this . - double GetDisplayStartTime(double time); + /// The point in time. + /// The amount of visible time. + /// The time at which enters . + double GetDisplayStartTime(double time, double timeRange); /// /// Computes the spatial length within a start and end time. /// /// The start time. /// The end time. + /// The amount of visible time. + /// The absolute spatial length through . /// The absolute spatial length. - float GetLength(double startTime, double endTime); + float GetLength(double startTime, double endTime, double timeRange, float scrollLength); /// /// Given the current time, computes the spatial position of a point in time. /// /// The time to compute the spatial position of. /// The current time. + /// The amount of visible time. + /// The absolute spatial length through . /// The absolute spatial position. - float PositionAt(double time, double currentTime); + float PositionAt(double time, double currentTime, double timeRange, float scrollLength); + + /// + /// Resets this to a default state. + /// + void Reset(); } } diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs index 1fbada4f4d..8b0eacc26b 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/OverlappingSpeedChangeVisualiser.cs @@ -6,40 +6,40 @@ using osu.Game.Rulesets.Timing; namespace osu.Game.Rulesets.UI.Scrolling.Visualisers { - public readonly struct OverlappingSpeedChangeVisualiser : ISpeedChangeVisualiser + public class OverlappingSpeedChangeVisualiser : ISpeedChangeVisualiser { private readonly MultiplierControlPoint searchPoint; private readonly SortedList controlPoints; - private readonly double timeRange; - private readonly float scrollLength; - public OverlappingSpeedChangeVisualiser(SortedList controlPoints, double timeRange, float scrollLength) + public OverlappingSpeedChangeVisualiser(SortedList controlPoints) { this.controlPoints = controlPoints; - this.timeRange = timeRange; - this.scrollLength = scrollLength; searchPoint = new MultiplierControlPoint(); } - public double GetDisplayStartTime(double time) + public double GetDisplayStartTime(double time, double timeRange) { // The total amount of time that the hitobject will remain visible within the timeRange, which decreases as the speed multiplier increases double visibleDuration = timeRange / controlPointAt(time).Multiplier; return time - visibleDuration; } - public float GetLength(double startTime, double endTime) + public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) { // At the hitobject's end time, the hitobject will be positioned such that its end rests at the origin. // This results in a negative-position value, and the absolute of it indicates the length of the hitobject. - return -PositionAt(startTime, endTime); + return -PositionAt(startTime, endTime, timeRange, scrollLength); } - public float PositionAt(double time, double currentTime) + public float PositionAt(double time, double currentTime, double timeRange, float scrollLength) => (float)((time - currentTime) / timeRange * controlPointAt(time).Multiplier * scrollLength); + public void Reset() + { + } + /// /// Finds the which affects the speed of hitobjects at a specific time. /// diff --git a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs index 1fa87a2f6b..bc63299bc8 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Visualisers/SequentialSpeedChangeVisualiser.cs @@ -7,45 +7,43 @@ using osu.Game.Rulesets.Timing; namespace osu.Game.Rulesets.UI.Scrolling.Visualisers { - public readonly struct SequentialSpeedChangeVisualiser : ISpeedChangeVisualiser + public class SequentialSpeedChangeVisualiser : ISpeedChangeVisualiser { private readonly Dictionary positionCache; private readonly IReadOnlyList controlPoints; - private readonly double timeRange; - private readonly float scrollLength; - public SequentialSpeedChangeVisualiser(IReadOnlyList controlPoints, double timeRange, float scrollLength) + public SequentialSpeedChangeVisualiser(IReadOnlyList controlPoints) { this.controlPoints = controlPoints; - this.timeRange = timeRange; - this.scrollLength = scrollLength; positionCache = new Dictionary(); } - public double GetDisplayStartTime(double time) => time - timeRange - 1000; + public double GetDisplayStartTime(double time, double timeRange) => time - timeRange - 1000; - public float GetLength(double startTime, double endTime) + public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) { - var objectLength = relativePositionAtCached(endTime) - relativePositionAtCached(startTime); + var objectLength = relativePositionAtCached(endTime, timeRange) - relativePositionAtCached(startTime, timeRange); return (float)(objectLength * scrollLength); } - public float PositionAt(double time, double currentTime) + public float PositionAt(double time, double currentTime, double timeRange, float scrollLength) { // Caching is not used here as currentTime is unlikely to have been previously cached - double timelinePosition = relativePositionAt(currentTime); - return (float)((relativePositionAtCached(time) - timelinePosition) * scrollLength); + double timelinePosition = relativePositionAt(currentTime, timeRange); + return (float)((relativePositionAtCached(time, timeRange) - timelinePosition) * scrollLength); } - private double relativePositionAtCached(double time) + private double relativePositionAtCached(double time, double timeRange) { if (!positionCache.TryGetValue(time, out double existing)) - positionCache[time] = existing = relativePositionAt(time); + positionCache[time] = existing = relativePositionAt(time, timeRange); return existing; } + public void Reset() => positionCache.Clear(); + /// /// Finds the position which corresponds to a point in time. /// This is a non-linear operation that depends on all the control points up to and including the one active at the time value. @@ -53,7 +51,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Visualisers /// The time to find the position at. /// The amount of time visualised by the scrolling area. /// A positive value indicating the position at . - private double relativePositionAt(double time) + private double relativePositionAt(double time, double timeRange) { if (controlPoints.Count == 0) return time / timeRange;