diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
index 97e3d82664..d1212096bf 100644
--- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
+++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs
@@ -12,6 +12,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
+using osu.Game.Rulesets.Osu.Utils;
using osuTK;
namespace osu.Game.Rulesets.Osu.Mods
@@ -23,15 +24,6 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Description => "It never gets boring!";
- // 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
- private const float playfield_edge_ratio = 0.375f;
-
- private static readonly float border_distance_x = OsuPlayfield.BASE_SIZE.X * playfield_edge_ratio;
- private static readonly float border_distance_y = OsuPlayfield.BASE_SIZE.Y * playfield_edge_ratio;
-
- private static readonly Vector2 playfield_middle = OsuPlayfield.BASE_SIZE / 2;
-
private static readonly float playfield_diagonal = OsuPlayfield.BASE_SIZE.LengthFast;
private Random rng;
@@ -113,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Mods
distanceToPrev * (float)Math.Sin(current.AngleRad)
);
- posRelativeToPrev = getRotatedVector(previous.EndPositionRandomised, posRelativeToPrev);
+ posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev);
current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X);
@@ -185,73 +177,6 @@ namespace osu.Game.Rulesets.Osu.Mods
}
}
- ///
- /// Determines the position of the current hit object relative to the previous one.
- ///
- /// The position of the current hit object relative to the previous one
- private Vector2 getRotatedVector(Vector2 prevPosChanged, Vector2 posRelativeToPrev)
- {
- var relativeRotationDistance = 0f;
-
- if (prevPosChanged.X < playfield_middle.X)
- {
- relativeRotationDistance = Math.Max(
- (border_distance_x - prevPosChanged.X) / border_distance_x,
- relativeRotationDistance
- );
- }
- else
- {
- relativeRotationDistance = Math.Max(
- (prevPosChanged.X - (OsuPlayfield.BASE_SIZE.X - border_distance_x)) / border_distance_x,
- relativeRotationDistance
- );
- }
-
- if (prevPosChanged.Y < playfield_middle.Y)
- {
- relativeRotationDistance = Math.Max(
- (border_distance_y - prevPosChanged.Y) / border_distance_y,
- relativeRotationDistance
- );
- }
- else
- {
- relativeRotationDistance = Math.Max(
- (prevPosChanged.Y - (OsuPlayfield.BASE_SIZE.Y - border_distance_y)) / border_distance_y,
- relativeRotationDistance
- );
- }
-
- return rotateVectorTowardsVector(posRelativeToPrev, playfield_middle - prevPosChanged, relativeRotationDistance / 2);
- }
-
- ///
- /// Rotates vector "initial" towards vector "destinantion"
- ///
- /// Vector to rotate to "destination"
- /// Vector "initial" should be rotated to
- /// The angle the vector should be rotated relative to the difference between the angles of the the two vectors.
- /// Resulting vector
- private Vector2 rotateVectorTowardsVector(Vector2 initial, Vector2 destination, float relativeDistance)
- {
- var initialAngleRad = Math.Atan2(initial.Y, initial.X);
- var destAngleRad = Math.Atan2(destination.Y, destination.X);
-
- var diff = destAngleRad - initialAngleRad;
-
- while (diff < -Math.PI) diff += 2 * Math.PI;
-
- while (diff > Math.PI) diff -= 2 * Math.PI;
-
- var finalAngleRad = initialAngleRad + relativeDistance * diff;
-
- return new Vector2(
- initial.Length * (float)Math.Cos(finalAngleRad),
- initial.Length * (float)Math.Sin(finalAngleRad)
- );
- }
-
private class RandomObjectInfo
{
public float AngleRad { get; set; }
diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs
new file mode 100644
index 0000000000..06b964a647
--- /dev/null
+++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs
@@ -0,0 +1,104 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using osu.Game.Rulesets.Osu.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Osu.Utils
+{
+ public static 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 closer the hit objects draw to the border, the sharper the turn
+ private const float playfield_edge_ratio = 0.375f;
+
+ private static readonly float border_distance_x = OsuPlayfield.BASE_SIZE.X * playfield_edge_ratio;
+ private static readonly float border_distance_y = OsuPlayfield.BASE_SIZE.Y * playfield_edge_ratio;
+
+ private static readonly Vector2 playfield_middle = OsuPlayfield.BASE_SIZE / 2;
+
+ ///
+ /// Rotate a hit object away from the playfield edge, while keeping a constant distance
+ /// from the previous object.
+ ///
+ ///
+ /// The extent of rotation depends on the position of the hit object. Hit objects
+ /// closer to the playfield edge will be rotated to a larger extent.
+ ///
+ /// Position of the previous hit object.
+ /// Position of the hit object to be rotated, relative to the previous hit object.
+ ///
+ /// The extent of rotation.
+ /// 0 means the hit object is never rotated.
+ /// 1 means the hit object will be fully rotated towards playfield center when it is originally at playfield edge.
+ ///
+ /// The new position of the hit object, relative to the previous one.
+ public static Vector2 RotateAwayFromEdge(Vector2 prevObjectPos, Vector2 posRelativeToPrev, float rotationRatio = 0.5f)
+ {
+ var relativeRotationDistance = 0f;
+
+ if (prevObjectPos.X < playfield_middle.X)
+ {
+ relativeRotationDistance = Math.Max(
+ (border_distance_x - prevObjectPos.X) / border_distance_x,
+ relativeRotationDistance
+ );
+ }
+ else
+ {
+ relativeRotationDistance = Math.Max(
+ (prevObjectPos.X - (OsuPlayfield.BASE_SIZE.X - border_distance_x)) / border_distance_x,
+ relativeRotationDistance
+ );
+ }
+
+ if (prevObjectPos.Y < playfield_middle.Y)
+ {
+ relativeRotationDistance = Math.Max(
+ (border_distance_y - prevObjectPos.Y) / border_distance_y,
+ relativeRotationDistance
+ );
+ }
+ else
+ {
+ relativeRotationDistance = Math.Max(
+ (prevObjectPos.Y - (OsuPlayfield.BASE_SIZE.Y - border_distance_y)) / border_distance_y,
+ relativeRotationDistance
+ );
+ }
+
+ return RotateVectorTowardsVector(
+ posRelativeToPrev,
+ playfield_middle - prevObjectPos,
+ Math.Min(1, relativeRotationDistance * rotationRatio)
+ );
+ }
+
+ ///
+ /// Rotates vector "initial" towards vector "destination".
+ ///
+ /// The vector to be rotated.
+ /// The vector that "initial" should be rotated towards.
+ /// How much "initial" should be rotated. 0 means no rotation. 1 means "initial" is fully rotated to equal "destination".
+ /// The rotated vector.
+ public static Vector2 RotateVectorTowardsVector(Vector2 initial, Vector2 destination, float rotationRatio)
+ {
+ var initialAngleRad = MathF.Atan2(initial.Y, initial.X);
+ var destAngleRad = MathF.Atan2(destination.Y, destination.X);
+
+ var diff = destAngleRad - initialAngleRad;
+
+ while (diff < -MathF.PI) diff += 2 * MathF.PI;
+
+ while (diff > MathF.PI) diff -= 2 * MathF.PI;
+
+ var finalAngleRad = initialAngleRad + rotationRatio * diff;
+
+ return new Vector2(
+ initial.Length * MathF.Cos(finalAngleRad),
+ initial.Length * MathF.Sin(finalAngleRad)
+ );
+ }
+ }
+}