1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-15 03:47:18 +08:00

Replace weird IntervalGroupedHitObjects inheritance layer

This commit is contained in:
tsunyoku 2025-01-21 15:58:33 +00:00
parent 1c4bc6dffd
commit 14c68bcc58
8 changed files with 152 additions and 134 deletions

View File

@ -1,64 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Utils;
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
{
/// <summary>
/// A base class for grouping <see cref="IHasInterval"/>s by their interval. In edges where an interval change
/// occurs, the <see cref="IHasInterval"/> is added to the group with the smaller interval.
/// </summary>
public abstract class IntervalGroupedHitObjects<TChildType>
where TChildType : IHasInterval
{
public IReadOnlyList<TChildType> Children { get; private set; }
/// <summary>
/// Create a new <see cref="IntervalGroupedHitObjects{TChildType}"/> from a list of <see cref="IHasInterval"/>s, and add
/// them to the <see cref="Children"/> list until the end of the group.
/// </summary>
/// <param name="data">The list of <see cref="IHasInterval"/>s.</param>
/// <param name="i">
/// Index in <paramref name="data"/> to start adding children. This will be modified and should be passed into
/// the next <see cref="IntervalGroupedHitObjects{TChildType}"/>'s constructor.
/// </param>
/// <param name="marginOfError">
/// The margin of error for the interval, within of which no interval change is considered to have occured.
/// </param>
protected IntervalGroupedHitObjects(List<TChildType> data, ref int i, double marginOfError)
{
List<TChildType> children = new List<TChildType>();
Children = children;
children.Add(data[i]);
i++;
for (; i < data.Count - 1; i++)
{
// An interval change occured, add the current data if the next interval is larger.
if (!Precision.AlmostEquals(data[i].Interval, data[i + 1].Interval, marginOfError))
{
if (data[i + 1].Interval > data[i].Interval + marginOfError)
{
children.Add(data[i]);
i++;
}
return;
}
// No interval change occured
children.Add(data[i]);
}
// Check if the last two objects in the data form a "flat" rhythm pattern within the specified margin of error.
// If true, add the current object to the group and increment the index to process the next object.
if (data.Count > 2 && Precision.AlmostEquals(data[^1].Interval, data[^2].Interval, marginOfError))
{
children.Add(data[i]);
i++;
}
}
}
}

View File

@ -9,9 +9,11 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
/// <summary>
/// Represents <see cref="SameRhythmGroupedHitObjects"/> grouped by their <see cref="SameRhythmGroupedHitObjects.StartTime"/>'s interval.
/// </summary>
public class SamePatternsGroupedHitObjects : IntervalGroupedHitObjects<SameRhythmGroupedHitObjects>
public class SamePatternsGroupedHitObjects
{
public SamePatternsGroupedHitObjects? Previous { get; private set; }
public IReadOnlyList<SameRhythmGroupedHitObjects> Children { get; }
public SamePatternsGroupedHitObjects? Previous { get; }
/// <summary>
/// The <see cref="SameRhythmGroupedHitObjects.Interval"/> between children <see cref="SameRhythmGroupedHitObjects"/> within this group.
@ -29,27 +31,10 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
public IEnumerable<TaikoDifficultyHitObject> AllHitObjects => Children.SelectMany(child => child.Children);
private SamePatternsGroupedHitObjects(SamePatternsGroupedHitObjects? previous, List<SameRhythmGroupedHitObjects> data, ref int i)
: base(data, ref i, 5)
public SamePatternsGroupedHitObjects(SamePatternsGroupedHitObjects? previous, List<SameRhythmGroupedHitObjects> children)
{
Previous = previous;
foreach (TaikoDifficultyHitObject hitObject in AllHitObjects)
{
hitObject.Rhythm.SamePatternsGroupedHitObjects = this;
}
}
public static void GroupPatterns(List<SameRhythmGroupedHitObjects> data)
{
List<SamePatternsGroupedHitObjects> samePatterns = new List<SamePatternsGroupedHitObjects>();
// Index does not need to be incremented, as it is handled within the IntervalGroupedHitObjects constructor.
for (int i = 0; i < data.Count;)
{
SamePatternsGroupedHitObjects? previous = samePatterns.Count > 0 ? samePatterns[^1] : null;
samePatterns.Add(new SamePatternsGroupedHitObjects(previous, data, ref i));
}
Children = children;
}
}
}

View File

@ -3,14 +3,17 @@
using System.Collections.Generic;
using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Difficulty.Utils;
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
{
/// <summary>
/// Represents a group of <see cref="TaikoDifficultyHitObject"/>s with no rhythm variation.
/// </summary>
public class SameRhythmGroupedHitObjects : IntervalGroupedHitObjects<TaikoDifficultyHitObject>, IHasInterval
public class SameRhythmGroupedHitObjects : IHasInterval
{
public List<TaikoDifficultyHitObject> Children { get; private set; }
public TaikoDifficultyHitObject FirstHitObject => Children[0];
public SameRhythmGroupedHitObjects? Previous;
@ -40,53 +43,21 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
/// <inheritdoc/>
public double Interval { get; private set; }
public SameRhythmGroupedHitObjects(SameRhythmGroupedHitObjects? previous, List<TaikoDifficultyHitObject> data, ref int i)
: base(data, ref i, 5)
public SameRhythmGroupedHitObjects(SameRhythmGroupedHitObjects? previous, List<TaikoDifficultyHitObject> children)
{
Previous = previous;
Children = children;
foreach (var hitObject in Children)
{
hitObject.Rhythm.SameRhythmGroupedHitObjects = this;
// Calculate the average interval between hitobjects, or null if there are fewer than two
HitObjectInterval = Children.Count < 2 ? null : Duration / (Children.Count - 1);
// Pass the HitObjectInterval to each child.
hitObject.HitObjectInterval = HitObjectInterval;
}
// Calculate the ratio between this group's interval and the previous group's interval
HitObjectIntervalRatio = Previous?.HitObjectInterval != null && HitObjectInterval != null
? HitObjectInterval.Value / Previous.HitObjectInterval.Value
: 1;
calculateIntervals();
}
public static List<SameRhythmGroupedHitObjects> GroupHitObjects(List<TaikoDifficultyHitObject> data)
{
List<SameRhythmGroupedHitObjects> flatPatterns = new List<SameRhythmGroupedHitObjects>();
// Index does not need to be incremented, as it is handled within IntervalGroupedHitObjects's constructor.
for (int i = 0; i < data.Count;)
{
SameRhythmGroupedHitObjects? previous = flatPatterns.Count > 0 ? flatPatterns[^1] : null;
flatPatterns.Add(new SameRhythmGroupedHitObjects(previous, data, ref i));
}
return flatPatterns;
}
private void calculateIntervals()
{
// Calculate the average interval between hitobjects, or null if there are fewer than two.
HitObjectInterval = Children.Count < 2 ? null : (Children[^1].StartTime - Children[0].StartTime) / (Children.Count - 1);
// If both the current and previous intervals are available, calculate the ratio.
if (Previous?.HitObjectInterval != null && HitObjectInterval != null)
{
HitObjectIntervalRatio = HitObjectInterval.Value / Previous.HitObjectInterval.Value;
}
if (Previous == null)
{
return;
}
Interval = StartTime - Previous.StartTime;
// Calculate the interval from the previous group's start time
Interval = Previous != null ? StartTime - Previous.StartTime : 0;
}
}
}

View File

@ -0,0 +1,63 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data;
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm
{
public static class TaikoRhythmDifficultyPreprocessor
{
public static void ProcessAndAssign(List<TaikoDifficultyHitObject> hitObjects)
{
var rhythmGroups = createSameRhythmGroupedHitObjects(hitObjects);
foreach (var rhythmGroup in rhythmGroups)
{
foreach (var hitObject in rhythmGroup.Children)
{
hitObject.Rhythm.SameRhythmGroupedHitObjects = rhythmGroup;
hitObject.HitObjectInterval = rhythmGroup.HitObjectInterval;
}
}
var patternGroups = createSamePatternGroupedHitObjects(rhythmGroups);
foreach (var patternGroup in patternGroups)
{
foreach (var hitObject in patternGroup.AllHitObjects)
{
hitObject.Rhythm.SamePatternsGroupedHitObjects = patternGroup;
}
}
}
private static List<SameRhythmGroupedHitObjects> createSameRhythmGroupedHitObjects(List<TaikoDifficultyHitObject> hitObjects)
{
var rhythmGroups = new List<SameRhythmGroupedHitObjects>();
var groups = IntervalGroupingUtils.GroupByInterval(hitObjects);
foreach (var group in groups)
{
var previous = rhythmGroups.Count > 0 ? rhythmGroups[^1] : null;
rhythmGroups.Add(new SameRhythmGroupedHitObjects(previous, group));
}
return rhythmGroups;
}
private static List<SamePatternsGroupedHitObjects> createSamePatternGroupedHitObjects(List<SameRhythmGroupedHitObjects> rhythmGroups)
{
var patternGroups = new List<SamePatternsGroupedHitObjects>();
var groups = IntervalGroupingUtils.GroupByInterval(rhythmGroups);
foreach (var group in groups)
{
var previous = patternGroups.Count > 0 ? patternGroups[^1] : null;
patternGroups.Add(new SamePatternsGroupedHitObjects(previous, group));
}
return patternGroups;
}
}
}

View File

@ -9,6 +9,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm;
using osu.Game.Rulesets.Taiko.Difficulty.Utils;
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing
{

View File

@ -13,7 +13,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Colour;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data;
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm;
using osu.Game.Rulesets.Taiko.Difficulty.Skills;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.Scoring;
@ -91,9 +91,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty
}
TaikoColourDifficultyPreprocessor.ProcessAndAssign(difficultyHitObjects);
var groupedHitObjects = SameRhythmGroupedHitObjects.GroupHitObjects(noteObjects);
SamePatternsGroupedHitObjects.GroupPatterns(groupedHitObjects);
TaikoRhythmDifficultyPreprocessor.ProcessAndAssign(noteObjects);
return difficultyHitObjects;
}

View File

@ -1,10 +1,10 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm
namespace osu.Game.Rulesets.Taiko.Difficulty.Utils
{
/// <summary>
/// The interface for hitobjects that provide an interval value.
/// The interface for objects that provide an interval value.
/// </summary>
public interface IHasInterval
{

View File

@ -0,0 +1,64 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Utils;
using osu.Game.Rulesets.Taiko.Difficulty.Utils;
namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing.Rhythm.Data
{
public static class IntervalGroupingUtils
{
public static List<List<T>> GroupByInterval<T>(IReadOnlyList<T> data, double marginOfError = 5) where T : IHasInterval
{
var groups = new List<List<T>>();
if (data.Count == 0)
return groups;
int i = 0;
while (i < data.Count)
{
var group = createGroup(data, ref i, marginOfError);
groups.Add(group);
}
return groups;
}
private static List<T> createGroup<T>(IReadOnlyList<T> data, ref int i, double marginOfError) where T : IHasInterval
{
var children = new List<T> { data[i] };
i++;
for (; i < data.Count - 1; i++)
{
// An interval change occured, add the current data if the next interval is larger.
if (!Precision.AlmostEquals(data[i].Interval, data[i + 1].Interval, marginOfError))
{
if (data[i + 1].Interval > data[i].Interval + marginOfError)
{
children.Add(data[i]);
i++;
}
return children;
}
// No interval change occurred
children.Add(data[i]);
}
// Check if the last two objects in the data form a "flat" rhythm pattern within the specified margin of error.
// If true, add the current object to the group and increment the index to process the next object.
if (data.Count > 2 && i < data.Count &&
Precision.AlmostEquals(data[^1].Interval, data[^2].Interval, marginOfError))
{
children.Add(data[i]);
i++;
}
return children;
}
}
}