Beispiel #1
0
        /// <summary>
        /// Move the randomised position of a hit circle so that it fits inside the playfield.
        /// </summary>
        /// <returns>The deviation from the original randomised position in order to fit within the playfield.</returns>
        private Vector2 clampHitCircleToPlayfield(HitCircle circle, RandomObjectInfo objectInfo)
        {
            var previousPosition = objectInfo.PositionRandomised;

            objectInfo.EndPositionRandomised = objectInfo.PositionRandomised = clampToPlayfieldWithPadding(
                objectInfo.PositionRandomised,
                (float)circle.Radius
                );

            circle.Position = objectInfo.PositionRandomised;

            return(objectInfo.PositionRandomised - previousPosition);
        }
Beispiel #2
0
        public void ApplyToBeatmap(IBeatmap beatmap)
        {
            if (!(beatmap is OsuBeatmap osuBeatmap))
            {
                return;
            }

            var hitObjects = osuBeatmap.HitObjects;

            Seed.Value ??= RNG.Next();

            rng = new Random((int)Seed.Value);

            RandomObjectInfo previous = null;

            float rateOfChangeMultiplier = 0;

            for (int i = 0; i < hitObjects.Count; i++)
            {
                var hitObject = hitObjects[i];

                var current = new RandomObjectInfo(hitObject);

                // rateOfChangeMultiplier only changes every i iterations to prevent shaky-line-shaped streams
                if (i % 3 == 0)
                {
                    rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1;
                }

                if (hitObject is Spinner)
                {
                    previous = null;
                    continue;
                }

                applyRandomisation(rateOfChangeMultiplier, previous, current);

                hitObject.Position = current.PositionRandomised;

                // update end position as it may have changed as a result of the position update.
                current.EndPositionRandomised = current.PositionRandomised;

                if (hitObject is Slider slider)
                {
                    moveSliderIntoPlayfield(slider, current);
                }

                previous = current;
            }
        }
Beispiel #3
0
        /// <summary>
        /// Moves the <see cref="Slider"/> and all necessary nested <see cref="OsuHitObject"/>s into the <see cref="OsuPlayfield"/> if they aren't already.
        /// </summary>
        private void moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo)
        {
            var minMargin = getMinSliderMargin(slider);

            slider.Position = new Vector2(
                Math.Clamp(slider.Position.X, minMargin.Left, OsuPlayfield.BASE_SIZE.X - minMargin.Right),
                Math.Clamp(slider.Position.Y, minMargin.Top, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom)
                );

            currentObjectInfo.PositionRandomised    = slider.Position;
            currentObjectInfo.EndPositionRandomised = slider.EndPosition;

            shiftNestedObjects(slider, currentObjectInfo.PositionRandomised - currentObjectInfo.PositionOriginal);
        }
Beispiel #4
0
        /// <summary>
        /// Returns the final position of the hit object
        /// </summary>
        /// <returns>Final position of the hit object</returns>
        private void applyRandomisation(float rateOfChangeMultiplier, RandomObjectInfo previous, RandomObjectInfo current)
        {
            if (previous == null)
            {
                var playfieldSize = OsuPlayfield.BASE_SIZE;

                current.AngleRad           = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI);
                current.PositionRandomised = new Vector2((float)rng.NextDouble() * playfieldSize.X, (float)rng.NextDouble() * playfieldSize.Y);

                return;
            }

            float distanceToPrev = Vector2.Distance(previous.EndPositionOriginal, current.PositionOriginal);

            // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object)
            // is proportional to the distance between the last and the current hit object
            // to allow jumps and prevent too sharp turns during streams.
            var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * distanceToPrev / playfield_diagonal;

            current.AngleRad = (float)randomAngleRad + previous.AngleRad;
            if (current.AngleRad < 0)
            {
                current.AngleRad += 2 * (float)Math.PI;
            }

            var posRelativeToPrev = new Vector2(
                distanceToPrev * (float)Math.Cos(current.AngleRad),
                distanceToPrev * (float)Math.Sin(current.AngleRad)
                );

            posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev);

            current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X);

            var position = previous.EndPositionRandomised + posRelativeToPrev;

            // Move hit objects back into the playfield if they are outside of it,
            // which would sometimes happen during big jumps otherwise.
            position.X = MathHelper.Clamp(position.X, 0, OsuPlayfield.BASE_SIZE.X);
            position.Y = MathHelper.Clamp(position.Y, 0, OsuPlayfield.BASE_SIZE.Y);

            current.PositionRandomised = position;
        }
Beispiel #5
0
        private List <RandomObjectInfo> randomiseObjects(IEnumerable <OsuHitObject> hitObjects)
        {
            Debug.Assert(rng != null, $"{nameof(ApplyToBeatmap)} was not called before randomising objects");

            var randomObjects         = new List <RandomObjectInfo>();
            RandomObjectInfo?previous = null;
            float            rateOfChangeMultiplier = 0;

            foreach (OsuHitObject hitObject in hitObjects)
            {
                var current = new RandomObjectInfo(hitObject);
                randomObjects.Add(current);

                // rateOfChangeMultiplier only changes every 5 iterations in a combo
                // to prevent shaky-line-shaped streams
                if (hitObject.IndexInCurrentCombo % 5 == 0)
                {
                    rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1;
                }

                if (previous == null)
                {
                    current.DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.X / 2);
                    current.RelativeAngle        = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI);
                }
                else
                {
                    current.DistanceFromPrevious = Vector2.Distance(previous.EndPositionOriginal, current.PositionOriginal);

                    // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object)
                    // is proportional to the distance between the last and the current hit object
                    // to allow jumps and prevent too sharp turns during streams.

                    // Allow maximum jump angle when jump distance is more than half of playfield diagonal length
                    current.RelativeAngle = rateOfChangeMultiplier * 2 * (float)Math.PI * Math.Min(1f, current.DistanceFromPrevious / (playfield_diagonal * 0.5f));
                }

                previous = current;
            }

            return(randomObjects);
        }
Beispiel #6
0
        /// <summary>
        /// Moves the <see cref="Slider"/> and all necessary nested <see cref="OsuHitObject"/>s into the <see cref="OsuPlayfield"/> if they aren't already.
        /// </summary>
        private void moveSliderIntoPlayfield(Slider slider, RandomObjectInfo currentObjectInfo)
        {
            // Min. distances from the slider's position to the playfield border
            var minMargin = new MarginPadding();

            foreach (var hitObject in slider.NestedHitObjects.Where(o => o is SliderTick || o is SliderEndCircle))
            {
                if (!(hitObject is OsuHitObject osuHitObject))
                {
                    continue;
                }

                var relativePos = Vector2.Subtract(osuHitObject.Position, slider.Position);

                minMargin.Left   = Math.Max(minMargin.Left, -relativePos.X);
                minMargin.Right  = Math.Max(minMargin.Right, relativePos.X);
                minMargin.Top    = Math.Max(minMargin.Top, -relativePos.Y);
                minMargin.Bottom = Math.Max(minMargin.Bottom, relativePos.Y);
            }

            if (slider.Position.X < minMargin.Left)
            {
                slider.Position = new Vector2(minMargin.Left, slider.Position.Y);
            }
            else if (slider.Position.X + minMargin.Right > OsuPlayfield.BASE_SIZE.X)
            {
                slider.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - minMargin.Right, slider.Position.Y);
            }

            if (slider.Position.Y < minMargin.Top)
            {
                slider.Position = new Vector2(slider.Position.X, minMargin.Top);
            }
            else if (slider.Position.Y + minMargin.Bottom > OsuPlayfield.BASE_SIZE.Y)
            {
                slider.Position = new Vector2(slider.Position.X, OsuPlayfield.BASE_SIZE.Y - minMargin.Bottom);
            }

            currentObjectInfo.PositionRandomised    = slider.Position;
            currentObjectInfo.EndPositionRandomised = slider.EndPosition;
        }
Beispiel #7
0
        /// <summary>
        /// Returns the final position of the hit object
        /// </summary>
        /// <returns>Final position of the hit object</returns>
        private void applyRandomisation(float rateOfChangeMultiplier, RandomObjectInfo previous, RandomObjectInfo current)
        {
            if (previous == null)
            {
                var playfieldSize = OsuPlayfield.BASE_SIZE;

                current.AngleRad           = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI);
                current.PositionRandomised = new Vector2((float)rng.NextDouble() * playfieldSize.X, (float)rng.NextDouble() * playfieldSize.Y);

                return;
            }

            float distanceToPrev = Vector2.Distance(previous.EndPositionOriginal, current.PositionOriginal);

            // The max. angle (relative to the angle of the vector pointing from the 2nd last to the last hit object)
            // is proportional to the distance between the last and the current hit object
            // to allow jumps and prevent too sharp turns during streams.

            // Allow maximum jump angle when jump distance is more than half of playfield diagonal length
            var randomAngleRad = rateOfChangeMultiplier * 2 * Math.PI * Math.Min(1f, distanceToPrev / (playfield_diagonal * 0.5f));

            current.AngleRad = (float)randomAngleRad + previous.AngleRad;
            if (current.AngleRad < 0)
            {
                current.AngleRad += 2 * (float)Math.PI;
            }

            var posRelativeToPrev = new Vector2(
                distanceToPrev * (float)Math.Cos(current.AngleRad),
                distanceToPrev * (float)Math.Sin(current.AngleRad)
                );

            posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(previous.EndPositionRandomised, posRelativeToPrev);

            current.AngleRad = (float)Math.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X);

            current.PositionRandomised = previous.EndPositionRandomised + posRelativeToPrev;
        }
Beispiel #8
0
        /// <summary>
        /// Moves the <see cref="Slider"/> and all necessary nested <see cref="OsuHitObject"/>s into the <see cref="OsuPlayfield"/> if they aren't already.
        /// </summary>
        /// <returns>The deviation from the original randomised position in order to fit within the playfield.</returns>
        private Vector2 clampSliderToPlayfield(Slider slider, RandomObjectInfo objectInfo)
        {
            var possibleMovementBounds = calculatePossibleMovementBounds(slider);

            var previousPosition = objectInfo.PositionRandomised;

            // Clamp slider position to the placement area
            // If the slider is larger than the playfield, force it to stay at the original position
            var newX = possibleMovementBounds.Width < 0
                ? objectInfo.PositionOriginal.X
                : Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right);

            var newY = possibleMovementBounds.Height < 0
                ? objectInfo.PositionOriginal.Y
                : Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom);

            slider.Position = objectInfo.PositionRandomised = new Vector2(newX, newY);
            objectInfo.EndPositionRandomised = slider.EndPosition;

            shiftNestedObjects(slider, objectInfo.PositionRandomised - objectInfo.PositionOriginal);

            return(objectInfo.PositionRandomised - previousPosition);
        }
Beispiel #9
0
        private void computeRandomisedPosition(RandomObjectInfo current, RandomObjectInfo?previous, RandomObjectInfo?beforePrevious)
        {
            float previousAbsoluteAngle = 0f;

            if (previous != null)
            {
                Vector2 earliestPosition = beforePrevious?.HitObject.EndPosition ?? playfield_centre;
                Vector2 relativePosition = previous.HitObject.Position - earliestPosition;
                previousAbsoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X);
            }

            float absoluteAngle = previousAbsoluteAngle + current.RelativeAngle;

            var posRelativeToPrev = new Vector2(
                current.DistanceFromPrevious * (float)Math.Cos(absoluteAngle),
                current.DistanceFromPrevious * (float)Math.Sin(absoluteAngle)
                );

            Vector2 lastEndPosition = previous?.EndPositionRandomised ?? playfield_centre;

            posRelativeToPrev = OsuHitObjectGenerationUtils.RotateAwayFromEdge(lastEndPosition, posRelativeToPrev);

            current.PositionRandomised = lastEndPosition + posRelativeToPrev;
        }
Beispiel #10
0
        public void ApplyToBeatmap(IBeatmap beatmap)
        {
            if (!(beatmap is OsuBeatmap osuBeatmap))
            {
                return;
            }

            var hitObjects = osuBeatmap.HitObjects;

            Seed.Value ??= RNG.Next();

            rng = new Random((int)Seed.Value);

            RandomObjectInfo previous = null;

            float rateOfChangeMultiplier = 0;

            for (int i = 0; i < hitObjects.Count; i++)
            {
                var hitObject = hitObjects[i];

                var current = new RandomObjectInfo(hitObject);

                // rateOfChangeMultiplier only changes every i iterations to prevent shaky-line-shaped streams
                if (i % 3 == 0)
                {
                    rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1;
                }

                if (hitObject is Spinner)
                {
                    previous = null;
                    continue;
                }

                applyRandomisation(rateOfChangeMultiplier, previous, current);

                hitObject.Position = current.PositionRandomised;

                // update end position as it may have changed as a result of the position update.
                current.EndPositionRandomised = current.PositionRandomised;

                switch (hitObject)
                {
                case Slider slider:
                    // Shift nested objects the same distance as the slider got shifted in the randomisation process
                    // so that moveSliderIntoPlayfield() can determine their relative distances to slider.Position and thus minMargin
                    shiftNestedObjects(slider, Vector2.Subtract(slider.Position, current.PositionOriginal));

                    var oldPos = new Vector2(slider.Position.X, slider.Position.Y);

                    moveSliderIntoPlayfield(slider, current);

                    // Shift them again to move them to their final position after the slider got moved into the playfield
                    shiftNestedObjects(slider, Vector2.Subtract(slider.Position, oldPos));

                    break;
                }

                previous = current;
            }
        }
Beispiel #11
0
        public void ApplyToBeatmap(IBeatmap beatmap)
        {
            if (!(beatmap is OsuBeatmap osuBeatmap))
            {
                return;
            }

            var hitObjects = osuBeatmap.HitObjects;

            Seed.Value ??= RNG.Next();

            rng = new Random((int)Seed.Value);

            RandomObjectInfo previous = null;

            float rateOfChangeMultiplier = 0;

            for (int i = 0; i < hitObjects.Count; i++)
            {
                var hitObject = hitObjects[i];

                var current = new RandomObjectInfo(hitObject);

                // rateOfChangeMultiplier only changes every 5 iterations in a combo
                // to prevent shaky-line-shaped streams
                if (hitObject.IndexInCurrentCombo % 5 == 0)
                {
                    rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1;
                }

                if (hitObject is Spinner)
                {
                    previous = null;
                    continue;
                }

                applyRandomisation(rateOfChangeMultiplier, previous, current);

                // Move hit objects back into the playfield if they are outside of it
                Vector2 shift = Vector2.Zero;

                switch (hitObject)
                {
                case HitCircle circle:
                    shift = clampHitCircleToPlayfield(circle, current);
                    break;

                case Slider slider:
                    shift = clampSliderToPlayfield(slider, current);
                    break;
                }

                if (shift != Vector2.Zero)
                {
                    var toBeShifted = new List <OsuHitObject>();

                    for (int j = i - 1; j >= i - preceding_hitobjects_to_shift && j >= 0; j--)
                    {
                        // only shift hit circles
                        if (!(hitObjects[j] is HitCircle))
                        {
                            break;
                        }

                        toBeShifted.Add(hitObjects[j]);
                    }

                    if (toBeShifted.Count > 0)
                    {
                        applyDecreasingShift(toBeShifted, shift);
                    }
                }

                previous = current;
            }
        }