Convert the position modifier to stateless methods

This commit is contained in:
Henry Lin 2022-03-10 11:53:03 +08:00
parent ede838c4b3
commit 3a71d81775
3 changed files with 35 additions and 40 deletions

View File

@ -35,18 +35,18 @@ public void ApplyToBeatmap(IBeatmap beatmap)
rng = new Random((int)Seed.Value); rng = new Random((int)Seed.Value);
var positionModifier = new OsuHitObjectPositionModifier(osuBeatmap.HitObjects); var positionInfos = OsuHitObjectGenerationUtils.GeneratePositionInfos(osuBeatmap.HitObjects);
float rateOfChangeMultiplier = 0; float rateOfChangeMultiplier = 0;
foreach (var positionInfo in positionModifier.ObjectPositionInfos) foreach (var positionInfo in positionInfos)
{ {
// rateOfChangeMultiplier only changes every 5 iterations in a combo // rateOfChangeMultiplier only changes every 5 iterations in a combo
// to prevent shaky-line-shaped streams // to prevent shaky-line-shaped streams
if (positionInfo.HitObject.IndexInCurrentCombo % 5 == 0) if (positionInfo.HitObject.IndexInCurrentCombo % 5 == 0)
rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1;
if (positionInfo == positionModifier.ObjectPositionInfos.First()) if (positionInfo == positionInfos.First())
{ {
positionInfo.DistanceFromPrevious = (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2; positionInfo.DistanceFromPrevious = (float)rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2;
positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI);
@ -57,7 +57,7 @@ public void ApplyToBeatmap(IBeatmap beatmap)
} }
} }
positionModifier.ApplyModifications(); osuBeatmap.HitObjects = OsuHitObjectGenerationUtils.RepositionHitObjects(positionInfos);
} }
} }
} }

View File

@ -11,7 +11,7 @@
namespace osu.Game.Rulesets.Osu.Utils namespace osu.Game.Rulesets.Osu.Utils
{ {
public static class OsuHitObjectGenerationUtils public static partial class OsuHitObjectGenerationUtils
{ {
// The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle. // The relative distance to the edge of the playfield before objects' positions should start to "turn around" and curve towards the middle.
// The closer the hit objects draw to the border, the sharper the turn // The closer the hit objects draw to the border, the sharper the turn

View File

@ -13,10 +13,7 @@
namespace osu.Game.Rulesets.Osu.Utils namespace osu.Game.Rulesets.Osu.Utils
{ {
/// <summary> public static partial class OsuHitObjectGenerationUtils
/// Places hit objects according to information in <see cref="ObjectPositionInfos"/> while keeping objects inside the playfield.
/// </summary>
public class OsuHitObjectPositionModifier
{ {
/// <summary> /// <summary>
/// Number of previous hitobjects to be shifted together when an object is being moved. /// Number of previous hitobjects to be shifted together when an object is being moved.
@ -25,24 +22,15 @@ public class OsuHitObjectPositionModifier
private static readonly Vector2 playfield_centre = OsuPlayfield.BASE_SIZE / 2; private static readonly Vector2 playfield_centre = OsuPlayfield.BASE_SIZE / 2;
private readonly List<OsuHitObject> hitObjects;
private readonly List<ObjectPositionInfo> objectPositionInfos = new List<ObjectPositionInfo>();
/// <summary> /// <summary>
/// Contains information specifying how each hit object should be placed. /// Generate a list of <see cref="IObjectPositionInfo"/>s containing information for how the given list of
/// <para>The default values correspond to how objects are originally placed in the beatmap.</para> /// <see cref="OsuHitObject"/>s are positioned.
/// </summary> /// </summary>
public IReadOnlyList<IObjectPositionInfo> ObjectPositionInfos => objectPositionInfos; /// <param name="hitObjects">A list of <see cref="OsuHitObject"/>s to process.</param>
/// <returns>A list of <see cref="IObjectPositionInfo"/>s describing how each hit object is positioned relative to the previous one.</returns>
public OsuHitObjectPositionModifier(List<OsuHitObject> hitObjects) public static List<IObjectPositionInfo> GeneratePositionInfos(IEnumerable<OsuHitObject> hitObjects)
{
this.hitObjects = hitObjects;
populateObjectPositionInfos();
}
private void populateObjectPositionInfos()
{ {
var positionInfos = new List<IObjectPositionInfo>();
Vector2 previousPosition = playfield_centre; Vector2 previousPosition = playfield_centre;
float previousAngle = 0; float previousAngle = 0;
@ -52,7 +40,7 @@ private void populateObjectPositionInfos()
float absoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X); float absoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X);
float relativeAngle = absoluteAngle - previousAngle; float relativeAngle = absoluteAngle - previousAngle;
objectPositionInfos.Add(new ObjectPositionInfo(hitObject) positionInfos.Add(new ObjectPositionInfo(hitObject)
{ {
RelativeAngle = relativeAngle, RelativeAngle = relativeAngle,
DistanceFromPrevious = relativePosition.Length DistanceFromPrevious = relativePosition.Length
@ -61,18 +49,23 @@ private void populateObjectPositionInfos()
previousPosition = hitObject.EndPosition; previousPosition = hitObject.EndPosition;
previousAngle = absoluteAngle; previousAngle = absoluteAngle;
} }
return positionInfos;
} }
/// <summary> /// <summary>
/// Reposition the hit objects according to the information in <see cref="ObjectPositionInfos"/>. /// Reposition the hit objects according to the information in <paramref name="objectPositionInfos"/>.
/// </summary> /// </summary>
public void ApplyModifications() /// <param name="objectPositionInfos"></param>
/// <returns>The repositioned hit objects.</returns>
public static List<OsuHitObject> RepositionHitObjects(IEnumerable<IObjectPositionInfo> objectPositionInfos)
{ {
List<ObjectPositionInfo> positionInfos = objectPositionInfos.Cast<ObjectPositionInfo>().ToList();
ObjectPositionInfo? previous = null; ObjectPositionInfo? previous = null;
for (int i = 0; i < objectPositionInfos.Count; i++) for (int i = 0; i < positionInfos.Count; i++)
{ {
var current = objectPositionInfos[i]; var current = positionInfos[i];
var hitObject = current.HitObject; var hitObject = current.HitObject;
if (hitObject is Spinner) if (hitObject is Spinner)
@ -81,7 +74,7 @@ public void ApplyModifications()
continue; continue;
} }
computeModifiedPosition(current, previous, i > 1 ? objectPositionInfos[i - 2] : null); computeModifiedPosition(current, previous, i > 1 ? positionInfos[i - 2] : null);
// Move hit objects back into the playfield if they are outside of it // Move hit objects back into the playfield if they are outside of it
Vector2 shift = Vector2.Zero; Vector2 shift = Vector2.Zero;
@ -104,9 +97,9 @@ public void ApplyModifications()
for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--) for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--)
{ {
// only shift hit circles // only shift hit circles
if (!(hitObjects[j] is HitCircle)) break; if (!(positionInfos[j].HitObject is HitCircle)) break;
toBeShifted.Add(hitObjects[j]); toBeShifted.Add(positionInfos[j].HitObject);
} }
if (toBeShifted.Count > 0) if (toBeShifted.Count > 0)
@ -115,6 +108,8 @@ public void ApplyModifications()
previous = current; previous = current;
} }
return positionInfos.Select(p => p.HitObject).ToList();
} }
/// <summary> /// <summary>
@ -123,7 +118,7 @@ public void ApplyModifications()
/// <param name="current">The <see cref="ObjectPositionInfo"/> representing the hit object to have the modified position computed for.</param> /// <param name="current">The <see cref="ObjectPositionInfo"/> representing the hit object to have the modified position computed for.</param>
/// <param name="previous">The <see cref="ObjectPositionInfo"/> representing the hit object immediately preceding the current one.</param> /// <param name="previous">The <see cref="ObjectPositionInfo"/> representing the hit object immediately preceding the current one.</param>
/// <param name="beforePrevious">The <see cref="ObjectPositionInfo"/> representing the hit object immediately preceding the <paramref name="previous"/> one.</param> /// <param name="beforePrevious">The <see cref="ObjectPositionInfo"/> representing the hit object immediately preceding the <paramref name="previous"/> one.</param>
private void computeModifiedPosition(ObjectPositionInfo current, ObjectPositionInfo? previous, ObjectPositionInfo? beforePrevious) private static void computeModifiedPosition(ObjectPositionInfo current, ObjectPositionInfo? previous, ObjectPositionInfo? beforePrevious)
{ {
float previousAbsoluteAngle = 0f; float previousAbsoluteAngle = 0f;
@ -143,7 +138,7 @@ private void computeModifiedPosition(ObjectPositionInfo current, ObjectPositionI
Vector2 lastEndPosition = previous?.EndPositionModified ?? playfield_centre; Vector2 lastEndPosition = previous?.EndPositionModified ?? playfield_centre;
posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(lastEndPosition, posRelativeToPrev); posRelativeToPrev = RotateAwayFromEdge(lastEndPosition, posRelativeToPrev);
current.PositionModified = lastEndPosition + posRelativeToPrev; current.PositionModified = lastEndPosition + posRelativeToPrev;
} }
@ -152,7 +147,7 @@ private void computeModifiedPosition(ObjectPositionInfo current, ObjectPositionI
/// Move the modified position of a hit circle so that it fits inside the playfield. /// Move the modified position of a hit circle so that it fits inside the playfield.
/// </summary> /// </summary>
/// <returns>The deviation from the original modified position in order to fit within the playfield.</returns> /// <returns>The deviation from the original modified position in order to fit within the playfield.</returns>
private Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfo objectPositionInfo) private static Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfo objectPositionInfo)
{ {
var previousPosition = objectPositionInfo.PositionModified; var previousPosition = objectPositionInfo.PositionModified;
objectPositionInfo.EndPositionModified = objectPositionInfo.PositionModified = clampToPlayfieldWithPadding( objectPositionInfo.EndPositionModified = objectPositionInfo.PositionModified = clampToPlayfieldWithPadding(
@ -169,7 +164,7 @@ private Vector2 clampHitCircleToPlayfield(HitCircle circle, ObjectPositionInfo o
/// Moves the <see cref="Slider"/> and all necessary nested <see cref="OsuHitObject"/>s into the <see cref="OsuPlayfield"/> if they aren't already. /// Moves the <see cref="Slider"/> and all necessary nested <see cref="OsuHitObject"/>s into the <see cref="OsuPlayfield"/> if they aren't already.
/// </summary> /// </summary>
/// <returns>The deviation from the original modified position in order to fit within the playfield.</returns> /// <returns>The deviation from the original modified position in order to fit within the playfield.</returns>
private Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfo objectPositionInfo) private static Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfo objectPositionInfo)
{ {
var possibleMovementBounds = calculatePossibleMovementBounds(slider); var possibleMovementBounds = calculatePossibleMovementBounds(slider);
@ -199,7 +194,7 @@ private Vector2 clampSliderToPlayfield(Slider slider, ObjectPositionInfo objectP
/// </summary> /// </summary>
/// <param name="hitObjects">The list of hit objects to be shifted.</param> /// <param name="hitObjects">The list of hit objects to be shifted.</param>
/// <param name="shift">The amount to be shifted.</param> /// <param name="shift">The amount to be shifted.</param>
private void applyDecreasingShift(IList<OsuHitObject> hitObjects, Vector2 shift) private static void applyDecreasingShift(IList<OsuHitObject> hitObjects, Vector2 shift)
{ {
for (int i = 0; i < hitObjects.Count; i++) for (int i = 0; i < hitObjects.Count; i++)
{ {
@ -219,7 +214,7 @@ private void applyDecreasingShift(IList<OsuHitObject> hitObjects, Vector2 shift)
/// <remarks> /// <remarks>
/// If the slider is larger than the playfield, the returned <see cref="RectangleF"/> may have negative width/height. /// If the slider is larger than the playfield, the returned <see cref="RectangleF"/> may have negative width/height.
/// </remarks> /// </remarks>
private RectangleF calculatePossibleMovementBounds(Slider slider) private static RectangleF calculatePossibleMovementBounds(Slider slider)
{ {
var pathPositions = new List<Vector2>(); var pathPositions = new List<Vector2>();
slider.Path.GetPathToProgress(pathPositions, 0, 1); slider.Path.GetPathToProgress(pathPositions, 0, 1);
@ -266,7 +261,7 @@ private RectangleF calculatePossibleMovementBounds(Slider slider)
/// </summary> /// </summary>
/// <param name="slider"><see cref="Slider"/> whose nested <see cref="SliderTick"/>s and <see cref="SliderRepeat"/>s should be shifted</param> /// <param name="slider"><see cref="Slider"/> whose nested <see cref="SliderTick"/>s and <see cref="SliderRepeat"/>s should be shifted</param>
/// <param name="shift">The <see cref="Vector2"/> the <see cref="Slider"/>'s nested <see cref="SliderTick"/>s and <see cref="SliderRepeat"/>s should be shifted by</param> /// <param name="shift">The <see cref="Vector2"/> the <see cref="Slider"/>'s nested <see cref="SliderTick"/>s and <see cref="SliderRepeat"/>s should be shifted by</param>
private void shiftNestedObjects(Slider slider, Vector2 shift) private static void shiftNestedObjects(Slider slider, Vector2 shift)
{ {
foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderRepeat)) foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderRepeat))
{ {
@ -283,7 +278,7 @@ private void shiftNestedObjects(Slider slider, Vector2 shift)
/// <param name="position">The position to be clamped.</param> /// <param name="position">The position to be clamped.</param>
/// <param name="padding">The minimum distance allowed from playfield edges.</param> /// <param name="padding">The minimum distance allowed from playfield edges.</param>
/// <returns>The clamped position.</returns> /// <returns>The clamped position.</returns>
private Vector2 clampToPlayfieldWithPadding(Vector2 position, float padding) private static Vector2 clampToPlayfieldWithPadding(Vector2 position, float padding)
{ {
return new Vector2( return new Vector2(
Math.Clamp(position.X, padding, OsuPlayfield.BASE_SIZE.X - padding), Math.Clamp(position.X, padding, OsuPlayfield.BASE_SIZE.X - padding),