From e6a931a5c8ad360039a0fab822b3b52dc43486d8 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sat, 5 Oct 2024 23:59:07 +0300 Subject: [PATCH 01/10] initial commit --- .../Difficulty/OsuDifficultyCalculator.cs | 11 ++ osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 8 + .../Localisation/HUD/SongProgressStrings.cs | 8 +- .../Difficulty/DifficultyCalculator.cs | 58 ++++++ .../Rulesets/Difficulty/Skills/StrainSkill.cs | 16 ++ .../Screens/Play/HUD/ArgonSongProgress.cs | 4 +- .../Screens/Play/HUD/DefaultSongProgress.cs | 25 ++- .../Play/HUD/DefaultSongProgressGraph.cs | 182 ++++++++++++++++-- osu.Game/Screens/Play/HUD/SongProgress.cs | 157 +++++++++++++-- osu.Game/Screens/Play/SquareGraph.cs | 8 +- 10 files changed, 418 insertions(+), 59 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index c4fcd1f760..237ec07560 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -142,6 +142,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty return skills.ToArray(); } + public override StrainSkill[] GetRelevantSkills(Skill[] skills) + { + var strainSkills = skills.OfType().ToList(); + + // Remove the second skill (SliderlessAim) from the list + if (strainSkills.Count > 1) + strainSkills.RemoveAt(1); + + return strainSkills.ToArray(); + } + protected override Mod[] DifficultyAdjustmentMods => new Mod[] { new OsuModTouchDevice(), diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 871faf5906..274dd5bd6c 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -156,6 +156,14 @@ namespace osu.Game.Beatmaps updateScheduler); } + public Task> GetSectionDifficultiesAsync(IWorkingBeatmap beatmap, Ruleset ruleset, Mod[] mods, CancellationToken cancellationToken = default) + { + return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateSectionDifficulties(mods, cancellationToken).ToList(), + cancellationToken, + TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, + updateScheduler); + } + /// /// Updates all tracked using the current ruleset and mods. /// diff --git a/osu.Game/Localisation/HUD/SongProgressStrings.cs b/osu.Game/Localisation/HUD/SongProgressStrings.cs index 332f15cb17..6909cb0286 100644 --- a/osu.Game/Localisation/HUD/SongProgressStrings.cs +++ b/osu.Game/Localisation/HUD/SongProgressStrings.cs @@ -10,14 +10,14 @@ namespace osu.Game.Localisation.HUD private const string prefix = @"osu.Game.Resources.Localisation.HUD.SongProgress"; /// - /// "Show difficulty graph" + /// "Difficulty graph type" /// - public static LocalisableString ShowGraph => new TranslatableString(getKey(@"show_graph"), "Show difficulty graph"); + public static LocalisableString GraphType => new TranslatableString(getKey(@"graph_type"), "Difficulty graph type"); /// - /// "Whether a graph displaying difficulty throughout the beatmap should be shown" + /// "Type of a graph displaying difficulty throughout the beatmap" /// - public static LocalisableString ShowGraphDescription => new TranslatableString(getKey(@"show_graph_description"), "Whether a graph displaying difficulty throughout the beatmap should be shown"); + public static LocalisableString GraphTypeDescription => new TranslatableString(getKey(@"graph_type_description"), "Type of a graph displaying difficulty throughout the beatmap"); /// /// "Show time" diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 63b27243d0..c45446b149 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -133,6 +133,64 @@ namespace osu.Game.Rulesets.Difficulty return attribs; } + /// + /// Calculates the difficulty of section of the beatmap with no mods applied. + /// + /// The cancellation token. + /// Per-skill array where each value represents difficulty of this section in certain skill. + public IEnumerable CalculateSectionDifficulties(CancellationToken cancellationToken = default) + => CalculateSectionDifficulties(Array.Empty(), cancellationToken); + + /// + /// Calculates the difficulty of section of the beatmap using a specific mod combination. + /// + /// The mods that should be applied to the beatmap. + /// The cancellation token. + /// Per-skill array where each value represents difficulty of this section in certain skill. + public IEnumerable CalculateSectionDifficulties([NotNull] IEnumerable mods, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + preProcess(mods, cancellationToken); + + var skills = CreateSkills(Beatmap, playableMods, clockRate); + StrainSkill[] relevantSkills = GetRelevantSkills(skills); + var hitObjects = getDifficultyHitObjects(); + + if (!hitObjects.Any()) + return Enumerable.Empty(); + + // Add sections before first object to preserve correct bounds + foreach (var skill in relevantSkills) + skill.AddEmptySections(hitObjects.First().StartTime, Beatmap.HitObjects.First().StartTime / clockRate); + + foreach (var hitObject in hitObjects) + { + foreach (var skill in relevantSkills) + { + cancellationToken.ThrowIfCancellationRequested(); + skill.Process(hitObject); + } + } + + // Add sections up to the end time of last object to preserve correct bounds + foreach (var skill in relevantSkills) + skill.AddEmptySections(Beatmap.HitObjects.Last().GetEndTime() / clockRate); + + var strainsForSkills = relevantSkills + .Select(skill => skill.GetCurrentStrainPeaks().ToArray()); + + return strainsForSkills; + } + + /// + /// Filters relevant skills that would be used to represent difficulty of the section. + /// + /// + /// IMPORTANT NOTE: for now it's using to receive section values, consider changing it in the future. + /// Also, this function should also be changed if filtering skills is no longer enough to get desired values. + /// + public virtual StrainSkill[] GetRelevantSkills(Skill[] skills) => skills.OfType().ToArray(); + /// /// Calculates the difficulty of the beatmap using all mod combinations applicable to the beatmap. /// diff --git a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs index b07e8399c0..037bcfcd22 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/StrainSkill.cs @@ -60,6 +60,22 @@ namespace osu.Game.Rulesets.Difficulty.Skills currentSectionPeak = Math.Max(StrainValueAt(current), currentSectionPeak); } + /// + /// Adds empty strain sections + /// + public void AddEmptySections(double time, double? currentSectionOverride = null) + { + if (currentSectionOverride.HasValue) + currentSectionEnd = Math.Ceiling(currentSectionOverride.Value / SectionLength) * SectionLength; + + while (time > currentSectionEnd) + { + saveCurrentPeak(); + currentSectionPeak = 0; // This is wrong, but there's no way get decay from this class + currentSectionEnd += SectionLength; + } + } + /// /// Saves the current peak strain level to the list of strain peaks, which will be used to calculate an overall difficulty. /// diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index 92ac863e98..ee17a345bf 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Play.HUD private const float bar_height = 10; - [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowGraph), nameof(SongProgressStrings.ShowGraphDescription))] + [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.GraphType), nameof(SongProgressStrings.GraphTypeDescription))] public Bindable ShowGraph { get; } = new BindableBool(true); [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Play.HUD AccentColour.BindValueChanged(_ => Colour = AccentColour.Value, true); } - protected override void UpdateObjects(IEnumerable objects) + protected override void UpdateFromObjects(IEnumerable objects) { graph.Objects = objects; diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 4e41901ee3..df5819c9a2 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -31,8 +31,8 @@ namespace osu.Game.Screens.Play.HUD private readonly SongProgressInfo info; private readonly Container content; - [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowGraph), nameof(SongProgressStrings.ShowGraphDescription))] - public Bindable ShowGraph { get; } = new BindableBool(true); + //[SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.GraphType), nameof(SongProgressStrings.GraphTypeDescription))] + //public Bindable GraphType { get; } = new Bindable(DifficultyGraphType.TotalStrain); [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); @@ -87,24 +87,29 @@ namespace osu.Game.Screens.Play.HUD protected override void LoadComplete() { + GraphType.ValueChanged += _ => updateGraphType(); + Interactive.BindValueChanged(_ => updateBarVisibility(), true); - ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); ShowTime.BindValueChanged(_ => updateTimeVisibility(), true); AccentColour.BindValueChanged(_ => Colour = AccentColour.Value, true); + updateGraphType(); + base.LoadComplete(); } - protected override void UpdateObjects(IEnumerable objects) + protected override void UpdateTimeBounds() { - graph.Objects = objects; - info.StartTime = FirstHitTime; info.EndTime = LastHitTime; bar.StartTime = FirstHitTime; bar.EndTime = LastHitTime; } + protected override void UpdateFromObjects(IEnumerable objects) => graph.SetFromObjects(objects); + + protected override void UpdateFromStrains(double[] sectionStrains) => graph.SetFromStrains(sectionStrains); + protected override void UpdateProgress(double progress, bool isIntro) { graph.Progress = isIntro ? 0 : (int)(graph.ColumnCount * progress); @@ -128,12 +133,12 @@ namespace osu.Game.Screens.Play.HUD updateInfoMargin(); } - private void updateGraphVisibility() + private void updateGraphType() { float barHeight = bottom_bar_height + handle_size.Y; - bar.ResizeHeightTo(ShowGraph.Value ? barHeight + graph_height : barHeight, transition_duration, Easing.In); - graph.FadeTo(ShowGraph.Value ? 1 : 0, transition_duration, Easing.In); + bar.ResizeHeightTo(GraphType.Value != DifficultyGraphType.None ? barHeight + graph_height : barHeight, transition_duration, Easing.In); + graph.FadeTo(GraphType.Value != DifficultyGraphType.None ? 1 : 0, transition_duration, Easing.In); updateInfoMargin(); } @@ -147,7 +152,7 @@ namespace osu.Game.Screens.Play.HUD private void updateInfoMargin() { - float finalMargin = bottom_bar_height + (Interactive.Value ? handle_size.Y : 0) + (ShowGraph.Value ? graph_height : 0); + float finalMargin = bottom_bar_height + (Interactive.Value ? handle_size.Y : 0) + (GraphType.Value != DifficultyGraphType.None ? graph_height : 0); info.TransformTo(nameof(info.Margin), new MarginPadding { Bottom = finalMargin }, transition_duration, Easing.In); } } diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgressGraph.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgressGraph.cs index 047c64a4a4..7e3ae504c0 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgressGraph.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgressGraph.cs @@ -13,39 +13,183 @@ namespace osu.Game.Screens.Play.HUD { public partial class DefaultSongProgressGraph : SquareGraph { - private IEnumerable objects; + private const int granularity = 200; - public IEnumerable Objects + public void SetFromObjects(IEnumerable objects) { - set + Values = new float[granularity]; + + if (!objects.Any()) + return; + + (double firstHit, double lastHit) = BeatmapExtensions.CalculatePlayableBounds(objects); + + if (lastHit == 0) + lastHit = objects.Last().StartTime; + + double interval = (lastHit - firstHit + 1) / granularity; + + foreach (var h in objects) { - objects = value; + double endTime = h.GetEndTime(); - const int granularity = 200; - Values = new int[granularity]; + Debug.Assert(endTime >= h.StartTime); - if (!objects.Any()) - return; + int startRange = (int)((h.StartTime - firstHit) / interval); + int endRange = (int)((endTime - firstHit) / interval); + for (int i = startRange; i <= endRange; i++) + Values[i]++; + } + } - (double firstHit, double lastHit) = BeatmapExtensions.CalculatePlayableBounds(objects); + public void SetFromStrains(double[] strains) + { + // For some reason it has 1 column delay, account for this by skipping first value + Values = resampling(strains, granularity).Select(value => (float)value).ToArray(); + } - if (lastHit == 0) - lastHit = objects.Last().StartTime; + private static double[] resampling(double[] values, int targetSize) + { + if (targetSize > values.Length) + return resamplingUpscale(values, targetSize); - double interval = (lastHit - firstHit + 1) / granularity; + else if (targetSize < values.Length) + return resamplingDownscale(values, targetSize); - foreach (var h in objects) + return (double[])values.Clone(); + } + + private static double[] resamplingUpscale(double[] values, int targetSize) + { + // Create array filled with -inf + double[] result = Enumerable.Repeat(double.NegativeInfinity, targetSize).ToArray(); + + // First and last peaks are constant + result[0] = values[0]; + result[^1] = values[^1]; + + // On the first pass we place peaks + + int sourceIndex = 1; + int targetIndex = 1; + + // Adjust sizes accounting for the fact that first and last elements already set-up + int sourceSize = values.Length - 1; + targetSize -= 1; + + for (; targetIndex < targetSize - 1; targetIndex++) + { + double sourceProgress = (double)sourceIndex / sourceSize; + + double targetProgressNext = (targetIndex + 1.0) / targetSize; + + // If we reached the point where source is between current and next - then peak is either current or next + if (sourceProgress <= targetProgressNext) { - double endTime = h.GetEndTime(); + double targetProgressCurrent = (double)targetIndex / targetSize; - Debug.Assert(endTime >= h.StartTime); + double distanceToCurrent = sourceProgress - targetProgressCurrent; + double distanceToNext = targetProgressNext - sourceProgress; - int startRange = (int)((h.StartTime - firstHit) / interval); - int endRange = (int)((endTime - firstHit) / interval); - for (int i = startRange; i <= endRange; i++) - Values[i]++; + // If it's next what is closer - abbadon current and move to next immediatly + if (distanceToNext < distanceToCurrent) + { + result[targetIndex] = double.NegativeInfinity; + targetIndex++; + } + + result[targetIndex] = values[sourceIndex]; + sourceIndex++; } } + + // On second pass we interpolate between peaks + + sourceIndex = 0; + targetIndex = 1; + + for (; targetIndex < targetSize; targetIndex++) + { + // If we're on peak - skip iteration + if (result[targetIndex] != double.NegativeInfinity) + { + sourceIndex++; + continue; + } + + double targetProgress = (double)targetIndex / targetSize; + + double previousPeakProgress = (double)sourceIndex / sourceSize; + double nextPeakProgress = (sourceIndex + 1.0) / sourceSize; + + double distanceToPreviousPeak = targetProgress - previousPeakProgress; + double distanceToNextPeak = nextPeakProgress - targetProgress; + + double lerpCoef = distanceToPreviousPeak / (distanceToPreviousPeak + distanceToNextPeak); + result[targetIndex] = double.Lerp(values[sourceIndex], values[sourceIndex + 1], lerpCoef); + } + + return result; + } + + private static double[] resamplingDownscale(double[] values, int targetSize) + { + double[] result = new double[targetSize]; + + int sourceIndex = 0; + int targetIndex = 0; + + double currentSampleMax = double.NegativeInfinity; + + for (; sourceIndex < values.Length; sourceIndex++) + { + double currentValue = values[sourceIndex]; + + double sourceProgress = (sourceIndex + 0.5) / values.Length; + double targetProgressBorder = (targetIndex + 1.0) / targetSize; + + double distanceToBorder = targetProgressBorder - sourceProgress; + + // Handle transition to next sample + if (distanceToBorder < 0) + { + double targetProgressCurrent = (targetIndex + 0.5) / targetSize; + double targetProgressNext = (targetIndex + 1.5) / targetSize; + + // Try fit weighted current into still current sample + // It would always be closer to Next than to Current + double weight = (targetProgressNext - sourceProgress) / (sourceProgress - targetProgressCurrent); + double weightedValue = currentValue * weight; + + if (currentSampleMax < weightedValue) currentSampleMax = weightedValue; + + // Flush current max + result[targetIndex] = currentSampleMax; + targetIndex++; + currentSampleMax = double.NegativeInfinity; + + // Try to fit weighted previous into future sample + if (sourceIndex > 0) + { + double prevValue = values[sourceIndex - 1]; + double sourceProgressPrev = (sourceIndex - 0.5) / values.Length; + + // It would always be closer to Current than to Current + weight = (sourceProgressPrev - targetProgressCurrent) / (targetProgressNext - sourceProgressPrev); + weightedValue = prevValue * weight; + + currentSampleMax = weightedValue; + } + } + + // Replace with maximum of the sample + if (currentSampleMax < currentValue) currentSampleMax = currentValue; + } + + // Flush last value + result[targetIndex] = currentSampleMax; + + return result; } } } diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index 296306ec89..72261646a3 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -3,18 +3,33 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Localisation.HUD; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { + public enum DifficultyGraphType + { + None, + ObjectDensity, + MaxStrain, + TotalStrain + } + public abstract partial class SongProgress : OverlayContainer, ISerialisableDrawable { // Some implementations of this element allow seeking during gameplay playback. @@ -32,6 +47,12 @@ namespace osu.Game.Screens.Play.HUD /// public readonly Bindable Interactive = new Bindable(); + /// + /// Type of the difficulty info used in graph. + /// + [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.GraphType), nameof(SongProgressStrings.GraphTypeDescription))] + public Bindable GraphType { get; } = new Bindable(DifficultyGraphType.None); + public bool UsesFixedAnchor { get; set; } [Resolved] @@ -46,20 +67,6 @@ namespace osu.Game.Screens.Play.HUD /// protected IClock FrameStableClock => frameStableClock ?? GameplayClock; - private IEnumerable? objects; - - public IEnumerable Objects - { - set - { - objects = value; - - (FirstHitTime, LastHitTime) = BeatmapExtensions.CalculatePlayableBounds(objects); - - UpdateObjects(objects); - } - } - protected override void LoadComplete() { base.LoadComplete(); @@ -67,10 +74,6 @@ namespace osu.Game.Screens.Play.HUD Show(); } - protected double FirstHitTime { get; private set; } - - protected double LastHitTime { get; private set; } - /// /// Called every update frame with current progress information. /// @@ -78,8 +81,6 @@ namespace osu.Game.Screens.Play.HUD /// If true, progress is (0..1) through the intro. protected abstract void UpdateProgress(double progress, bool isIntro); - protected virtual void UpdateObjects(IEnumerable objects) { } - [BackgroundDependencyLoader] private void load(DrawableRuleset? drawableRuleset, Player? player) { @@ -90,6 +91,32 @@ namespace osu.Game.Screens.Play.HUD Objects = drawableRuleset.Objects; } + + GraphType.BindValueChanged(_ => updateBasedOnGraphType(), true); + } + + private void updateBasedOnGraphType() + { + switch (GraphType.Value) + { + case DifficultyGraphType.None: + UpdateFromObjects(Enumerable.Empty()); + break; + + case DifficultyGraphType.ObjectDensity: + if (objects != null) UpdateFromObjects(objects); + break; + + case DifficultyGraphType.MaxStrain: + if (sectionStrains != null) UpdateFromStrains(getMaxStrains(sectionStrains)); + else calculateStrains(); + break; + + case DifficultyGraphType.TotalStrain: + if (sectionStrains != null) UpdateFromStrains(getTotalStrains(sectionStrains)); + else calculateStrains(); + break; + } } protected override void PopIn() => this.FadeIn(500, Easing.OutQuint); @@ -127,5 +154,95 @@ namespace osu.Game.Screens.Play.HUD UpdateProgress(objectOffsetCurrent / objectDuration, false); } } + + protected virtual void UpdateTimeBounds() { } + + #region object density + + protected double FirstHitTime { get; private set; } + + protected double LastHitTime { get; private set; } + + private IEnumerable? objects; + + public IEnumerable Objects + { + set + { + objects = value; + + (FirstHitTime, LastHitTime) = BeatmapExtensions.CalculatePlayableBounds(objects); + + UpdateTimeBounds(); + updateBasedOnGraphType(); + } + } + + protected virtual void UpdateFromObjects(IEnumerable objects) { } + + #endregion + + + #region diffcalc + + private bool strainsCalculationWasStarted = false; + + private List? sectionStrains; + + [Resolved] + private BeatmapDifficultyCache difficultyCache { get; set; } = null!; + + [Resolved] + private IBindable beatmap { get; set; } = null!; + + [Resolved] + private IBindable ruleset { get; set; } = null!; + + [Resolved] + private IBindable> mods { get; set; } = null!; + + private void calculateStrains() + { + // No need for another recalc if strains are being recalculated right now; + if (strainsCalculationWasStarted) return; + + strainsCalculationWasStarted = true; + difficultyCache.GetSectionDifficultiesAsync(beatmap.Value, ruleset.Value.CreateInstance(), mods.Value.ToArray()) + .ContinueWith(task => Schedule(() => + { + sectionStrains = task.GetResultSafely(); + updateBasedOnGraphType(); + }), TaskContinuationOptions.OnlyOnRanToCompletion); + } + + private double[] getMaxStrains(List allStrains) + { + var result = allStrains + .SelectMany(arr => arr.Select((value, index) => (value, index))) + .GroupBy(x => x.index) + .Select(g => g.Max(x => x.value)); + + return convertStrains(result); + } + + private double[] getTotalStrains(List allStrains) + { + var result = allStrains + .SelectMany(arr => arr.Select((value, index) => (value, index))) + .GroupBy(x => x.index) + .Select(g => Math.Sqrt(g.Sum(x => x.value * x.value))); + + return convertStrains(result); + } + + // Strains are ending with StartTime of last object, so we need to add + private double[] convertStrains(IEnumerable strains) + { + return strains.ToArray(); + } + + protected virtual void UpdateFromStrains(double[] sectionStrains) { } + + #endregion } } diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index 0c7b485755..40ce5af207 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -43,9 +43,9 @@ namespace osu.Game.Screens.Play private float[] calculatedValues = Array.Empty(); // values but adjusted to fit the amount of columns - private int[] values; + private float[] values; - public int[] Values + public float[] Values { get => values; set @@ -168,13 +168,13 @@ namespace osu.Game.Screens.Play return; } - int max = values.Max(); + float max = values.Max(); float step = values.Length / (float)ColumnCount; for (float i = 0; i < values.Length; i += step) { - newValues.Add((float)values[(int)i] / max); + newValues.Add(values[(int)i] / max); } calculatedValues = newValues.ToArray(); From c839203a0dbc6ee8dd4bb077699869099d3dd48d Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 6 Oct 2024 00:28:07 +0300 Subject: [PATCH 02/10] added strain to argon --- .../Screens/Play/HUD/ArgonSongProgress.cs | 16 +- .../Play/HUD/ArgonSongProgressGraph.cs | 59 ++++--- .../Play/HUD/DefaultSongProgressGraph.cs | 148 +---------------- osu.Game/Screens/Play/HUD/SongProgress.cs | 10 +- osu.Game/Utils/FormatUtils.cs | 152 ++++++++++++++++++ 5 files changed, 193 insertions(+), 192 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index ee17a345bf..251e6c9c34 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -24,9 +24,6 @@ namespace osu.Game.Screens.Play.HUD private const float bar_height = 10; - [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.GraphType), nameof(SongProgressStrings.GraphTypeDescription))] - public Bindable ShowGraph { get; } = new BindableBool(true); - [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); @@ -95,23 +92,26 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); + GraphType.ValueChanged += _ => updateGraphVisibility(); + Interactive.BindValueChanged(_ => bar.Interactive = Interactive.Value, true); - ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); ShowTime.BindValueChanged(_ => info.FadeTo(ShowTime.Value ? 1 : 0, 200, Easing.In), true); AccentColour.BindValueChanged(_ => Colour = AccentColour.Value, true); } - protected override void UpdateFromObjects(IEnumerable objects) + protected override void UpdateTimeBounds() { - graph.Objects = objects; - info.StartTime = bar.StartTime = FirstHitTime; info.EndTime = bar.EndTime = LastHitTime; } + protected override void UpdateFromObjects(IEnumerable objects) => graph.SetFromObjects(objects); + + protected override void UpdateFromStrains(double[] sectionStrains) => graph.SetFromStrains(sectionStrains); + private void updateGraphVisibility() { - graph.FadeTo(ShowGraph.Value ? 1 : 0, 200, Easing.In); + graph.FadeTo(GraphType.Value != DifficultyGraphType.None ? 1 : 0, 200, Easing.In); } protected override void Update() diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressGraph.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressGraph.cs index be570c1578..50ec364c84 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressGraph.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressGraph.cs @@ -10,49 +10,48 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Graphics.UserInterface; +using osu.Game.Utils; namespace osu.Game.Screens.Play.HUD { - public partial class ArgonSongProgressGraph : SegmentedGraph + public partial class ArgonSongProgressGraph : SegmentedGraph { private const int tier_count = 5; private const int display_granularity = 200; - private IEnumerable? objects; - - public IEnumerable Objects + public void SetFromObjects(IEnumerable objects) { - set + float[] values = new float[display_granularity]; + + if (!objects.Any()) + return; + + (double firstHit, double lastHit) = BeatmapExtensions.CalculatePlayableBounds(objects); + + if (lastHit == 0) + lastHit = objects.Last().StartTime; + + double interval = (lastHit - firstHit + 1) / display_granularity; + + foreach (var h in objects) { - objects = value; + double endTime = h.GetEndTime(); - int[] values = new int[display_granularity]; + Debug.Assert(endTime >= h.StartTime); - if (!objects.Any()) - return; - - (double firstHit, double lastHit) = BeatmapExtensions.CalculatePlayableBounds(objects); - - if (lastHit == 0) - lastHit = objects.Last().StartTime; - - double interval = (lastHit - firstHit + 1) / display_granularity; - - foreach (var h in objects) - { - double endTime = h.GetEndTime(); - - Debug.Assert(endTime >= h.StartTime); - - int startRange = (int)((h.StartTime - firstHit) / interval); - int endRange = (int)((endTime - firstHit) / interval); - for (int i = startRange; i <= endRange; i++) - values[i]++; - } - - Values = values; + int startRange = (int)((h.StartTime - firstHit) / interval); + int endRange = (int)((endTime - firstHit) / interval); + for (int i = startRange; i <= endRange; i++) + values[i]++; } + + Values = values; + } + + public void SetFromStrains(double[] strains) + { + Values = FormatUtils.ResampleStrains(strains, display_granularity).Select(value => (float)value).ToArray(); } public ArgonSongProgressGraph() diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgressGraph.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgressGraph.cs index 7e3ae504c0..50808e172b 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgressGraph.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgressGraph.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Diagnostics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; +using osu.Game.Utils; namespace osu.Game.Screens.Play.HUD { @@ -44,152 +45,7 @@ namespace osu.Game.Screens.Play.HUD public void SetFromStrains(double[] strains) { - // For some reason it has 1 column delay, account for this by skipping first value - Values = resampling(strains, granularity).Select(value => (float)value).ToArray(); - } - - private static double[] resampling(double[] values, int targetSize) - { - if (targetSize > values.Length) - return resamplingUpscale(values, targetSize); - - else if (targetSize < values.Length) - return resamplingDownscale(values, targetSize); - - return (double[])values.Clone(); - } - - private static double[] resamplingUpscale(double[] values, int targetSize) - { - // Create array filled with -inf - double[] result = Enumerable.Repeat(double.NegativeInfinity, targetSize).ToArray(); - - // First and last peaks are constant - result[0] = values[0]; - result[^1] = values[^1]; - - // On the first pass we place peaks - - int sourceIndex = 1; - int targetIndex = 1; - - // Adjust sizes accounting for the fact that first and last elements already set-up - int sourceSize = values.Length - 1; - targetSize -= 1; - - for (; targetIndex < targetSize - 1; targetIndex++) - { - double sourceProgress = (double)sourceIndex / sourceSize; - - double targetProgressNext = (targetIndex + 1.0) / targetSize; - - // If we reached the point where source is between current and next - then peak is either current or next - if (sourceProgress <= targetProgressNext) - { - double targetProgressCurrent = (double)targetIndex / targetSize; - - double distanceToCurrent = sourceProgress - targetProgressCurrent; - double distanceToNext = targetProgressNext - sourceProgress; - - // If it's next what is closer - abbadon current and move to next immediatly - if (distanceToNext < distanceToCurrent) - { - result[targetIndex] = double.NegativeInfinity; - targetIndex++; - } - - result[targetIndex] = values[sourceIndex]; - sourceIndex++; - } - } - - // On second pass we interpolate between peaks - - sourceIndex = 0; - targetIndex = 1; - - for (; targetIndex < targetSize; targetIndex++) - { - // If we're on peak - skip iteration - if (result[targetIndex] != double.NegativeInfinity) - { - sourceIndex++; - continue; - } - - double targetProgress = (double)targetIndex / targetSize; - - double previousPeakProgress = (double)sourceIndex / sourceSize; - double nextPeakProgress = (sourceIndex + 1.0) / sourceSize; - - double distanceToPreviousPeak = targetProgress - previousPeakProgress; - double distanceToNextPeak = nextPeakProgress - targetProgress; - - double lerpCoef = distanceToPreviousPeak / (distanceToPreviousPeak + distanceToNextPeak); - result[targetIndex] = double.Lerp(values[sourceIndex], values[sourceIndex + 1], lerpCoef); - } - - return result; - } - - private static double[] resamplingDownscale(double[] values, int targetSize) - { - double[] result = new double[targetSize]; - - int sourceIndex = 0; - int targetIndex = 0; - - double currentSampleMax = double.NegativeInfinity; - - for (; sourceIndex < values.Length; sourceIndex++) - { - double currentValue = values[sourceIndex]; - - double sourceProgress = (sourceIndex + 0.5) / values.Length; - double targetProgressBorder = (targetIndex + 1.0) / targetSize; - - double distanceToBorder = targetProgressBorder - sourceProgress; - - // Handle transition to next sample - if (distanceToBorder < 0) - { - double targetProgressCurrent = (targetIndex + 0.5) / targetSize; - double targetProgressNext = (targetIndex + 1.5) / targetSize; - - // Try fit weighted current into still current sample - // It would always be closer to Next than to Current - double weight = (targetProgressNext - sourceProgress) / (sourceProgress - targetProgressCurrent); - double weightedValue = currentValue * weight; - - if (currentSampleMax < weightedValue) currentSampleMax = weightedValue; - - // Flush current max - result[targetIndex] = currentSampleMax; - targetIndex++; - currentSampleMax = double.NegativeInfinity; - - // Try to fit weighted previous into future sample - if (sourceIndex > 0) - { - double prevValue = values[sourceIndex - 1]; - double sourceProgressPrev = (sourceIndex - 0.5) / values.Length; - - // It would always be closer to Current than to Current - weight = (sourceProgressPrev - targetProgressCurrent) / (targetProgressNext - sourceProgressPrev); - weightedValue = prevValue * weight; - - currentSampleMax = weightedValue; - } - } - - // Replace with maximum of the sample - if (currentSampleMax < currentValue) currentSampleMax = currentValue; - } - - // Flush last value - result[targetIndex] = currentSampleMax; - - return result; + Values = FormatUtils.ResampleStrains(strains, granularity).Select(value => (float)value).ToArray(); } } } diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index 72261646a3..c442212169 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -222,7 +222,7 @@ namespace osu.Game.Screens.Play.HUD .GroupBy(x => x.index) .Select(g => g.Max(x => x.value)); - return convertStrains(result); + return result.ToArray(); } private double[] getTotalStrains(List allStrains) @@ -232,13 +232,7 @@ namespace osu.Game.Screens.Play.HUD .GroupBy(x => x.index) .Select(g => Math.Sqrt(g.Sum(x => x.value * x.value))); - return convertStrains(result); - } - - // Strains are ending with StartTime of last object, so we need to add - private double[] convertStrains(IEnumerable strains) - { - return strains.ToArray(); + return result.ToArray(); } protected virtual void UpdateFromStrains(double[] sectionStrains) { } diff --git a/osu.Game/Utils/FormatUtils.cs b/osu.Game/Utils/FormatUtils.cs index cccad3711c..4e1e32f8a3 100644 --- a/osu.Game/Utils/FormatUtils.cs +++ b/osu.Game/Utils/FormatUtils.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using Humanizer; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Localisation; @@ -59,5 +60,156 @@ namespace osu.Game.Utils /// The base BPM to round. /// Rate adjustment, if applicable. public static int RoundBPM(double baseBpm, double rate = 1) => (int)Math.Round(Math.Round(baseBpm) * rate); + + /// + /// Resampling strain values to certain bin size. + /// + /// + /// The main feature of this resampling is that peak strains will be always preserved. + /// This means that the highest strain can't be decreased by averaging or interpolation. + /// + public static double[] ResampleStrains(double[] values, int targetSize) + { + if (targetSize > values.Length) + return resamplingUpscale(values, targetSize); + + else if (targetSize < values.Length) + return resamplingDownscale(values, targetSize); + + return (double[])values.Clone(); + } + + private static double[] resamplingUpscale(double[] values, int targetSize) + { + // Create array filled with -inf + double[] result = Enumerable.Repeat(double.NegativeInfinity, targetSize).ToArray(); + + // First and last peaks are constant + result[0] = values[0]; + result[^1] = values[^1]; + + // On the first pass we place peaks + + int sourceIndex = 1; + int targetIndex = 1; + + // Adjust sizes accounting for the fact that first and last elements already set-up + int sourceSize = values.Length - 1; + targetSize -= 1; + + for (; targetIndex < targetSize - 1; targetIndex++) + { + double sourceProgress = (double)sourceIndex / sourceSize; + + double targetProgressNext = (targetIndex + 1.0) / targetSize; + + // If we reached the point where source is between current and next - then peak is either current or next + if (sourceProgress <= targetProgressNext) + { + double targetProgressCurrent = (double)targetIndex / targetSize; + + double distanceToCurrent = sourceProgress - targetProgressCurrent; + double distanceToNext = targetProgressNext - sourceProgress; + + // If it's next what is closer - abbadon current and move to next immediatly + if (distanceToNext < distanceToCurrent) + { + result[targetIndex] = double.NegativeInfinity; + targetIndex++; + } + + result[targetIndex] = values[sourceIndex]; + sourceIndex++; + } + } + + // On second pass we interpolate between peaks + + sourceIndex = 0; + targetIndex = 1; + + for (; targetIndex < targetSize; targetIndex++) + { + // If we're on peak - skip iteration + if (result[targetIndex] != double.NegativeInfinity) + { + sourceIndex++; + continue; + } + + double targetProgress = (double)targetIndex / targetSize; + + double previousPeakProgress = (double)sourceIndex / sourceSize; + double nextPeakProgress = (sourceIndex + 1.0) / sourceSize; + + double distanceToPreviousPeak = targetProgress - previousPeakProgress; + double distanceToNextPeak = nextPeakProgress - targetProgress; + + double lerpCoef = distanceToPreviousPeak / (distanceToPreviousPeak + distanceToNextPeak); + result[targetIndex] = double.Lerp(values[sourceIndex], values[sourceIndex + 1], lerpCoef); + } + + return result; + } + + private static double[] resamplingDownscale(double[] values, int targetSize) + { + double[] result = new double[targetSize]; + + int sourceIndex = 0; + int targetIndex = 0; + + double currentSampleMax = double.NegativeInfinity; + + for (; sourceIndex < values.Length; sourceIndex++) + { + double currentValue = values[sourceIndex]; + + double sourceProgress = (sourceIndex + 0.5) / values.Length; + double targetProgressBorder = (targetIndex + 1.0) / targetSize; + + double distanceToBorder = targetProgressBorder - sourceProgress; + + // Handle transition to next sample + if (distanceToBorder < 0) + { + double targetProgressCurrent = (targetIndex + 0.5) / targetSize; + double targetProgressNext = (targetIndex + 1.5) / targetSize; + + // Try fit weighted current into still current sample + // It would always be closer to Next than to Current + double weight = (targetProgressNext - sourceProgress) / (sourceProgress - targetProgressCurrent); + double weightedValue = currentValue * weight; + + if (currentSampleMax < weightedValue) currentSampleMax = weightedValue; + + // Flush current max + result[targetIndex] = currentSampleMax; + targetIndex++; + currentSampleMax = double.NegativeInfinity; + + // Try to fit weighted previous into future sample + if (sourceIndex > 0) + { + double prevValue = values[sourceIndex - 1]; + double sourceProgressPrev = (sourceIndex - 0.5) / values.Length; + + // It would always be closer to Current than to Current + weight = (sourceProgressPrev - targetProgressCurrent) / (targetProgressNext - sourceProgressPrev); + weightedValue = prevValue * weight; + + currentSampleMax = weightedValue; + } + } + + // Replace with maximum of the sample + if (currentSampleMax < currentValue) currentSampleMax = currentValue; + } + + // Flush last value + result[targetIndex] = currentSampleMax; + + return result; + } } } From 809e4c57e1e9664496da8a36ba6f9a5e2b6eace5 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 6 Oct 2024 00:37:49 +0300 Subject: [PATCH 03/10] fix settings --- osu.Game/Screens/Play/HUD/ArgonSongProgress.cs | 9 +++++++-- .../Screens/Play/HUD/DefaultSongProgress.cs | 18 ++++++++++-------- osu.Game/Screens/Play/HUD/SongProgress.cs | 7 +++---- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index 251e6c9c34..0072926490 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -24,6 +24,9 @@ namespace osu.Game.Screens.Play.HUD private const float bar_height = 10; + [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.GraphType), nameof(SongProgressStrings.GraphTypeDescription))] + public Bindable GraphType { get; } = new Bindable(DifficultyGraphType.TotalStrain); + [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); @@ -92,7 +95,9 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - GraphType.ValueChanged += _ => updateGraphVisibility(); + GraphTypeInternal.ValueChanged += _ => updateGraphVisibility(); + GraphTypeInternal.Value = GraphType.Value; + GraphTypeInternal.BindTo(GraphType); Interactive.BindValueChanged(_ => bar.Interactive = Interactive.Value, true); ShowTime.BindValueChanged(_ => info.FadeTo(ShowTime.Value ? 1 : 0, 200, Easing.In), true); @@ -111,7 +116,7 @@ namespace osu.Game.Screens.Play.HUD private void updateGraphVisibility() { - graph.FadeTo(GraphType.Value != DifficultyGraphType.None ? 1 : 0, 200, Easing.In); + graph.FadeTo(GraphTypeInternal.Value != DifficultyGraphType.None ? 1 : 0, 200, Easing.In); } protected override void Update() diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index df5819c9a2..140f6d883c 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -31,8 +31,8 @@ namespace osu.Game.Screens.Play.HUD private readonly SongProgressInfo info; private readonly Container content; - //[SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.GraphType), nameof(SongProgressStrings.GraphTypeDescription))] - //public Bindable GraphType { get; } = new Bindable(DifficultyGraphType.TotalStrain); + [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.GraphType), nameof(SongProgressStrings.GraphTypeDescription))] + public Bindable GraphType { get; } = new Bindable(DifficultyGraphType.TotalStrain); [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); @@ -87,13 +87,15 @@ namespace osu.Game.Screens.Play.HUD protected override void LoadComplete() { - GraphType.ValueChanged += _ => updateGraphType(); + GraphTypeInternal.ValueChanged += _ => updateGraphVisibility(); + GraphTypeInternal.Value = GraphType.Value; + GraphTypeInternal.BindTo(GraphType); Interactive.BindValueChanged(_ => updateBarVisibility(), true); ShowTime.BindValueChanged(_ => updateTimeVisibility(), true); AccentColour.BindValueChanged(_ => Colour = AccentColour.Value, true); - updateGraphType(); + updateGraphVisibility(); base.LoadComplete(); } @@ -133,12 +135,12 @@ namespace osu.Game.Screens.Play.HUD updateInfoMargin(); } - private void updateGraphType() + private void updateGraphVisibility() { float barHeight = bottom_bar_height + handle_size.Y; - bar.ResizeHeightTo(GraphType.Value != DifficultyGraphType.None ? barHeight + graph_height : barHeight, transition_duration, Easing.In); - graph.FadeTo(GraphType.Value != DifficultyGraphType.None ? 1 : 0, transition_duration, Easing.In); + bar.ResizeHeightTo(GraphTypeInternal.Value != DifficultyGraphType.None ? barHeight + graph_height : barHeight, transition_duration, Easing.In); + graph.FadeTo(GraphTypeInternal.Value != DifficultyGraphType.None ? 1 : 0, transition_duration, Easing.In); updateInfoMargin(); } @@ -152,7 +154,7 @@ namespace osu.Game.Screens.Play.HUD private void updateInfoMargin() { - float finalMargin = bottom_bar_height + (Interactive.Value ? handle_size.Y : 0) + (GraphType.Value != DifficultyGraphType.None ? graph_height : 0); + float finalMargin = bottom_bar_height + (Interactive.Value ? handle_size.Y : 0) + (GraphTypeInternal.Value != DifficultyGraphType.None ? graph_height : 0); info.TransformTo(nameof(info.Margin), new MarginPadding { Bottom = finalMargin }, transition_duration, Easing.In); } } diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index c442212169..1e4ee07fbb 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -50,8 +50,7 @@ namespace osu.Game.Screens.Play.HUD /// /// Type of the difficulty info used in graph. /// - [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.GraphType), nameof(SongProgressStrings.GraphTypeDescription))] - public Bindable GraphType { get; } = new Bindable(DifficultyGraphType.None); + protected readonly Bindable GraphTypeInternal = new Bindable(DifficultyGraphType.None); public bool UsesFixedAnchor { get; set; } @@ -92,12 +91,12 @@ namespace osu.Game.Screens.Play.HUD Objects = drawableRuleset.Objects; } - GraphType.BindValueChanged(_ => updateBasedOnGraphType(), true); + GraphTypeInternal.BindValueChanged(_ => updateBasedOnGraphType(), true); } private void updateBasedOnGraphType() { - switch (GraphType.Value) + switch (GraphTypeInternal.Value) { case DifficultyGraphType.None: UpdateFromObjects(Enumerable.Empty()); From f898cfb0866975f30f40196b037e4335f60685ab Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 6 Oct 2024 00:49:24 +0300 Subject: [PATCH 04/10] fixed crash on 1 object maps --- osu.Game/Utils/FormatUtils.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Utils/FormatUtils.cs b/osu.Game/Utils/FormatUtils.cs index 4e1e32f8a3..0168babd19 100644 --- a/osu.Game/Utils/FormatUtils.cs +++ b/osu.Game/Utils/FormatUtils.cs @@ -70,6 +70,10 @@ namespace osu.Game.Utils /// public static double[] ResampleStrains(double[] values, int targetSize) { + // Set to at least one value, what will be 0 in this case + if (values.Length == 0) + values = new double[1]; + if (targetSize > values.Length) return resamplingUpscale(values, targetSize); @@ -94,7 +98,7 @@ namespace osu.Game.Utils int targetIndex = 1; // Adjust sizes accounting for the fact that first and last elements already set-up - int sourceSize = values.Length - 1; + int sourceSize = Math.Max(1, values.Length - 1); targetSize -= 1; for (; targetIndex < targetSize - 1; targetIndex++) @@ -146,7 +150,8 @@ namespace osu.Game.Utils double distanceToNextPeak = nextPeakProgress - targetProgress; double lerpCoef = distanceToPreviousPeak / (distanceToPreviousPeak + distanceToNextPeak); - result[targetIndex] = double.Lerp(values[sourceIndex], values[sourceIndex + 1], lerpCoef); + double nextValue = sourceIndex + 1 < values.Length ? values[sourceIndex + 1] : values[sourceIndex]; + result[targetIndex] = double.Lerp(values[sourceIndex], nextValue, lerpCoef); } return result; From f42b07a4eea74f6dac1c7ac9a818f04d59b990b9 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 6 Oct 2024 01:00:33 +0300 Subject: [PATCH 05/10] fixed CI --- .../Visual/Gameplay/TestSceneDefaultSongProgressGraph.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs | 4 ++-- osu.Game/Screens/Play/HUD/ArgonSongProgressGraph.cs | 2 +- osu.Game/Screens/Play/HUD/SongProgress.cs | 2 -- osu.Game/Utils/FormatUtils.cs | 1 - 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDefaultSongProgressGraph.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDefaultSongProgressGraph.cs index 66671a506f..4134d8ffae 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDefaultSongProgressGraph.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDefaultSongProgressGraph.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual.Gameplay for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000) objects.Add(new HitObject { StartTime = i }); - graph.Objects = objects; + graph.SetFromObjects(objects); } private partial class TestSongProgressGraph : DefaultSongProgressGraph diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index 99f0ffb9d0..b0284f347e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -91,8 +91,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddToggleStep("toggle graph", b => { - applyToDefaultProgress(s => s.ShowGraph.Value = b); - applyToArgonProgress(s => s.ShowGraph.Value = b); + applyToDefaultProgress(s => s.GraphType.Value = b ? DifficultyGraphType.ObjectDensity : DifficultyGraphType.None); + applyToArgonProgress(s => s.GraphType.Value = b ? DifficultyGraphType.ObjectDensity : DifficultyGraphType.None); }); AddStep("set white background", () => background.FadeColour(Color4.White, 200, Easing.OutQuint)); diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgressGraph.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgressGraph.cs index 50ec364c84..bb58fbc723 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgressGraph.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgressGraph.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Play.HUD } Values = values; - } + } public void SetFromStrains(double[] strains) { diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index 1e4ee07fbb..11d24e628c 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -12,8 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Localisation.HUD; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; diff --git a/osu.Game/Utils/FormatUtils.cs b/osu.Game/Utils/FormatUtils.cs index 0168babd19..798b16bf34 100644 --- a/osu.Game/Utils/FormatUtils.cs +++ b/osu.Game/Utils/FormatUtils.cs @@ -76,7 +76,6 @@ namespace osu.Game.Utils if (targetSize > values.Length) return resamplingUpscale(values, targetSize); - else if (targetSize < values.Length) return resamplingDownscale(values, targetSize); From f69b5bc28af9153a7beb69ce914c2eb29f69fb0b Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 6 Oct 2024 01:22:44 +0300 Subject: [PATCH 06/10] fixed CI again --- osu.Game/Screens/Play/HUD/SongProgress.cs | 29 +++++++---------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index 11d24e628c..5872f6415a 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -179,10 +179,9 @@ namespace osu.Game.Screens.Play.HUD #endregion - #region diffcalc - private bool strainsCalculationWasStarted = false; + private bool strainsCalculationWasStarted; private List? sectionStrains; @@ -205,29 +204,19 @@ namespace osu.Game.Screens.Play.HUD strainsCalculationWasStarted = true; difficultyCache.GetSectionDifficultiesAsync(beatmap.Value, ruleset.Value.CreateInstance(), mods.Value.ToArray()) - .ContinueWith(task => Schedule(() => - { - sectionStrains = task.GetResultSafely(); - updateBasedOnGraphType(); - }), TaskContinuationOptions.OnlyOnRanToCompletion); - } - - private double[] getMaxStrains(List allStrains) - { - var result = allStrains - .SelectMany(arr => arr.Select((value, index) => (value, index))) - .GroupBy(x => x.index) - .Select(g => g.Max(x => x.value)); - - return result.ToArray(); + .ContinueWith(task => Schedule(() => + { + sectionStrains = task.GetResultSafely(); + updateBasedOnGraphType(); + }), TaskContinuationOptions.OnlyOnRanToCompletion); } private double[] getTotalStrains(List allStrains) { var result = allStrains - .SelectMany(arr => arr.Select((value, index) => (value, index))) - .GroupBy(x => x.index) - .Select(g => Math.Sqrt(g.Sum(x => x.value * x.value))); + .SelectMany(arr => arr.Select((value, index) => (value, index))) + .GroupBy(x => x.index) + .Select(g => Math.Sqrt(g.Sum(x => x.value * x.value))); return result.ToArray(); } From ede8d81d26a7a1120eb220a12ea78c3d3dc8484c Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 6 Oct 2024 01:23:02 +0300 Subject: [PATCH 07/10] removed `MaxStrain` and renamed `TotalStrain` to `Difficulty` --- osu.Game/Screens/Play/HUD/ArgonSongProgress.cs | 2 +- osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 2 +- osu.Game/Screens/Play/HUD/SongProgress.cs | 10 ++-------- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index 0072926490..2629aa5e80 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Play.HUD private const float bar_height = 10; [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.GraphType), nameof(SongProgressStrings.GraphTypeDescription))] - public Bindable GraphType { get; } = new Bindable(DifficultyGraphType.TotalStrain); + public Bindable GraphType { get; } = new Bindable(DifficultyGraphType.Difficulty); [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 140f6d883c..0530b7199d 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Play.HUD private readonly Container content; [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.GraphType), nameof(SongProgressStrings.GraphTypeDescription))] - public Bindable GraphType { get; } = new Bindable(DifficultyGraphType.TotalStrain); + public Bindable GraphType { get; } = new Bindable(DifficultyGraphType.Difficulty); [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index 5872f6415a..76a85a7c2b 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -24,8 +24,7 @@ namespace osu.Game.Screens.Play.HUD { None, ObjectDensity, - MaxStrain, - TotalStrain + Difficulty } public abstract partial class SongProgress : OverlayContainer, ISerialisableDrawable @@ -104,12 +103,7 @@ namespace osu.Game.Screens.Play.HUD if (objects != null) UpdateFromObjects(objects); break; - case DifficultyGraphType.MaxStrain: - if (sectionStrains != null) UpdateFromStrains(getMaxStrains(sectionStrains)); - else calculateStrains(); - break; - - case DifficultyGraphType.TotalStrain: + case DifficultyGraphType.Difficulty: if (sectionStrains != null) UpdateFromStrains(getTotalStrains(sectionStrains)); else calculateStrains(); break; From 7a96fef38d3d6e8f03b81bb7098beeee066480e8 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 6 Oct 2024 12:50:54 +0300 Subject: [PATCH 08/10] changed from `GetRelevanSkills` to `IsRelevant` --- .../Difficulty/OsuDifficultyCalculator.cs | 11 ----------- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 11 +---------- osu.Game/Rulesets/Difficulty/Skills/Skill.cs | 5 +++++ 4 files changed, 8 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 237ec07560..c4fcd1f760 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -142,17 +142,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty return skills.ToArray(); } - public override StrainSkill[] GetRelevantSkills(Skill[] skills) - { - var strainSkills = skills.OfType().ToList(); - - // Remove the second skill (SliderlessAim) from the list - if (strainSkills.Count > 1) - strainSkills.RemoveAt(1); - - return strainSkills.ToArray(); - } - protected override Mod[] DifficultyAdjustmentMods => new Mod[] { new OsuModTouchDevice(), diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 1fbe03395c..58e8468c05 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -37,5 +37,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills return currentStrain; } + + public override bool IsRelevant => withSliders; } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index c45446b149..6bc47a95c4 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Difficulty preProcess(mods, cancellationToken); var skills = CreateSkills(Beatmap, playableMods, clockRate); - StrainSkill[] relevantSkills = GetRelevantSkills(skills); + StrainSkill[] relevantSkills = skills.OfType().Where(s => s.IsRelevant).ToArray(); var hitObjects = getDifficultyHitObjects(); if (!hitObjects.Any()) @@ -182,15 +182,6 @@ namespace osu.Game.Rulesets.Difficulty return strainsForSkills; } - /// - /// Filters relevant skills that would be used to represent difficulty of the section. - /// - /// - /// IMPORTANT NOTE: for now it's using to receive section values, consider changing it in the future. - /// Also, this function should also be changed if filtering skills is no longer enough to get desired values. - /// - public virtual StrainSkill[] GetRelevantSkills(Skill[] skills) => skills.OfType().ToArray(); - /// /// Calculates the difficulty of the beatmap using all mod combinations applicable to the beatmap. /// diff --git a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs index 8b8892113b..458772e031 100644 --- a/osu.Game/Rulesets/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -37,5 +37,10 @@ namespace osu.Game.Rulesets.Difficulty.Skills /// Returns the calculated difficulty value representing all s that have been processed up to this point. /// public abstract double DifficultyValue(); + + /// + /// Returns true if this Skill is relevant to star rating calculation. + /// + public virtual bool IsRelevant => true; } } From 510b8d96b1ee873296175a5866794d1bf0871f55 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Sun, 6 Oct 2024 12:51:28 +0300 Subject: [PATCH 09/10] offseted strains by one to decrease delay --- osu.Game/Screens/Play/HUD/SongProgress.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index 76a85a7c2b..4c5bcd377b 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -212,7 +212,8 @@ namespace osu.Game.Screens.Play.HUD .GroupBy(x => x.index) .Select(g => Math.Sqrt(g.Sum(x => x.value * x.value))); - return result.ToArray(); + // Skip one to account for delay in strains + return result.Skip(1).ToArray(); } protected virtual void UpdateFromStrains(double[] sectionStrains) { } From 4c128c79252ae148e910380e35125e467cc638a6 Mon Sep 17 00:00:00 2001 From: Givikap120 Date: Wed, 23 Oct 2024 12:43:43 +0300 Subject: [PATCH 10/10] changed defaults back to density this should fix tests failing --- osu.Game/Screens/Play/HUD/ArgonSongProgress.cs | 2 +- osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs index 056998ea28..ab3e551eeb 100644 --- a/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/ArgonSongProgress.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Play.HUD private const float bar_height = 10; [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.GraphType), nameof(SongProgressStrings.GraphTypeDescription))] - public Bindable GraphType { get; } = new Bindable(DifficultyGraphType.Difficulty); + public Bindable GraphType { get; } = new Bindable(DifficultyGraphType.ObjectDensity); [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true); diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 1d9fb9e36d..a47cd1ee2d 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Play.HUD private readonly Container content; [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.GraphType), nameof(SongProgressStrings.GraphTypeDescription))] - public Bindable GraphType { get; } = new Bindable(DifficultyGraphType.Difficulty); + public Bindable GraphType { get; } = new Bindable(DifficultyGraphType.ObjectDensity); [SettingSource(typeof(SongProgressStrings), nameof(SongProgressStrings.ShowTime), nameof(SongProgressStrings.ShowTimeDescription))] public Bindable ShowTime { get; } = new BindableBool(true);