/// <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; }
public void ApplyToHitObject(HitObject hitObject) { var osuObject = (OsuHitObject)hitObject; switch (Reflection.Value) { case MirrorType.Horizontal: OsuHitObjectGenerationUtils.ReflectHorizontally(osuObject); break; case MirrorType.Vertical: OsuHitObjectGenerationUtils.ReflectVertically(osuObject); break; case MirrorType.Both: OsuHitObjectGenerationUtils.ReflectHorizontally(osuObject); OsuHitObjectGenerationUtils.ReflectVertically(osuObject); break; } }
/// <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; }
public void ApplyToBeatmap(IBeatmap beatmap) { if (!(beatmap is OsuBeatmap osuBeatmap)) { return; } Seed.Value ??= RNG.Next(); rng = new Random((int)Seed.Value); var positionInfos = OsuHitObjectGenerationUtils.GeneratePositionInfos(osuBeatmap.HitObjects); float rateOfChangeMultiplier = 0; foreach (var positionInfo in positionInfos) { // rateOfChangeMultiplier only changes every 5 iterations in a combo // to prevent shaky-line-shaped streams if (positionInfo.HitObject.IndexInCurrentCombo % 5 == 0) { rateOfChangeMultiplier = (float)rng.NextDouble() * 2 - 1; } if (positionInfo == positionInfos.First()) { positionInfo.DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.X / 2); positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI); } else { positionInfo.RelativeAngle = rateOfChangeMultiplier * 2 * (float)Math.PI * Math.Min(1f, positionInfo.DistanceFromPrevious / (playfield_diagonal * 0.5f)); } } osuBeatmap.HitObjects = OsuHitObjectGenerationUtils.RepositionHitObjects(positionInfos); }
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; }
private void randomizeCirclePos(IReadOnlyList <OsuHitObject> hitObjects) { if (hitObjects.Count == 0) { return; } float nextSingle(float max = 1f) => (float)(rng.NextDouble() * max); const float two_pi = MathF.PI * 2; float direction = two_pi * nextSingle(); int maxComboIndex = hitObjects.Last().ComboIndex; for (int i = 0; i < hitObjects.Count; i++) { var obj = hitObjects[i]; var lastPos = i == 0 ? Vector2.Divide(OsuPlayfield.BASE_SIZE, 2) : hitObjects[i - 1].Position; float distance = maxComboIndex == 0 ? (float)obj.Radius : mapRange(obj.ComboIndex, 0, maxComboIndex, (float)obj.Radius, max_base_distance); if (obj.NewCombo) { distance *= 1.5f; } if (obj.Kiai) { distance *= 1.2f; } distance = Math.Min(distance_cap, distance); // Attempt to place the circle at a place that does not overlap with previous ones int tryCount = 0; // for checking overlap var precedingObjects = hitObjects.SkipLast(hitObjects.Count - i).TakeLast(overlap_check_count).ToList(); do { if (tryCount > 0) { direction = two_pi * nextSingle(); } var relativePos = new Vector2( distance * MathF.Cos(direction), distance * MathF.Sin(direction) ); // Rotate the new circle away from playfield border relativePos = OsuHitObjectGenerationUtils.RotateAwayFromEdge(lastPos, relativePos, edge_rotation_multiplier); direction = MathF.Atan2(relativePos.Y, relativePos.X); var newPosition = Vector2.Add(lastPos, relativePos); obj.Position = newPosition; clampToPlayfield(obj); tryCount++; if (tryCount % 10 == 0) { distance *= 0.9f; } } while (distance >= obj.Radius * 2 && checkForOverlap(precedingObjects, obj)); if (obj.LastInCombo) { direction = two_pi * nextSingle(); } else { direction += distance / distance_cap * (nextSingle() * two_pi - MathF.PI); } } }
public void ApplyToHitObject(HitObject hitObject) { var osuObject = (OsuHitObject)hitObject; OsuHitObjectGenerationUtils.ReflectVertically(osuObject); }