1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 10:52:53 +08:00

Merge pull request #1957 from smoogipoo/fix-repeat-confusion

Clean up off-by-one offsets from repeat-related properties
This commit is contained in:
Dean Herbert 2018-01-23 16:14:59 +09:00 committed by GitHub
commit 098cfa7107
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 111 additions and 120 deletions

View File

@ -21,9 +21,7 @@ namespace osu.Game.Rulesets.Catch.Objects
/// </summary>
private const float base_scoring_distance = 100;
public readonly SliderCurve Curve = new SliderCurve();
public int RepeatCount { get; set; } = 1;
public int RepeatCount { get; set; }
public double Velocity;
public double TickDistance;
@ -55,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Objects
var length = Curve.Distance;
var tickDistance = Math.Min(TickDistance, length);
var repeatDuration = length / Velocity;
var spanDuration = length / Velocity;
var minDistanceFromEnd = Velocity * 0.01;
@ -67,10 +65,10 @@ namespace osu.Game.Rulesets.Catch.Objects
X = X
});
for (var repeat = 0; repeat < RepeatCount; repeat++)
for (var span = 0; span < this.SpanCount(); span++)
{
var repeatStartTime = StartTime + repeat * repeatDuration;
var reversed = repeat % 2 == 1;
var spanStartTime = StartTime + span * spanDuration;
var reversed = span % 2 == 1;
for (var d = tickDistance; d <= length; d += tickDistance)
{
@ -80,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Objects
var timeProgress = d / length;
var distanceProgress = reversed ? 1 - timeProgress : timeProgress;
var lastTickTime = repeatStartTime + timeProgress * repeatDuration;
var lastTickTime = spanStartTime + timeProgress * spanDuration;
AddNested(new Droplet
{
StartTime = lastTickTime,
@ -95,17 +93,17 @@ namespace osu.Game.Rulesets.Catch.Objects
});
}
double tinyTickInterval = tickDistance / length * repeatDuration;
double tinyTickInterval = tickDistance / length * spanDuration;
while (tinyTickInterval > 100)
tinyTickInterval /= 2;
for (double t = 0; t < repeatDuration; t += tinyTickInterval)
for (double t = 0; t < spanDuration; t += tinyTickInterval)
{
double progress = reversed ? 1 - t / repeatDuration : t / repeatDuration;
double progress = reversed ? 1 - t / spanDuration : t / spanDuration;
AddNested(new TinyDroplet
{
StartTime = repeatStartTime + t,
StartTime = spanStartTime + t,
ComboColour = ComboColour,
X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
@ -121,15 +119,15 @@ namespace osu.Game.Rulesets.Catch.Objects
{
Samples = Samples,
ComboColour = ComboColour,
StartTime = repeatStartTime + repeatDuration,
StartTime = spanStartTime + spanDuration,
X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
});
}
}
public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity;
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
public float EndX => Curve.PositionAt(ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH;
public float EndX => Curve.PositionAt(this.ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH;
public double Duration => EndTime - StartTime;
@ -139,6 +137,8 @@ namespace osu.Game.Rulesets.Catch.Objects
set { Curve.Distance = value; }
}
public SliderCurve Curve { get; } = new SliderCurve();
public List<Vector2> ControlPoints
{
get { return Curve.ControlPoints; }
@ -152,17 +152,5 @@ namespace osu.Game.Rulesets.Catch.Objects
get { return Curve.CurveType; }
set { Curve.CurveType = value; }
}
public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress));
public double ProgressAt(double progress)
{
double p = progress * RepeatCount % 1;
if (RepeatAt(progress) % 2 == 1)
p = 1 - p;
return p;
}
public int RepeatAt(double progress) => (int)(progress * RepeatCount);
}
}

View File

@ -216,7 +216,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
if (curveData == null)
return HitObject.Samples;
double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.RepeatCount;
double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount();
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
return curveData.RepeatSamples[index];

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private readonly double endTime;
private readonly double segmentDuration;
private readonly int repeatCount;
private readonly int spanCount;
private PatternType convertType;
@ -39,25 +39,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var distanceData = hitObject as IHasDistance;
var repeatsData = hitObject as IHasRepeats;
repeatCount = repeatsData?.RepeatCount ?? 1;
spanCount = repeatsData?.SpanCount() ?? 1;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
// The true distance, accounting for any repeats
double distance = (distanceData?.Distance ?? 0) * repeatCount;
double distance = (distanceData?.Distance ?? 0) * spanCount;
// The velocity of the osu! hit object - calculated as the velocity of a slider
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength;
// The duration of the osu! hit object
double osuDuration = distance / osuVelocity;
endTime = hitObject.StartTime + osuDuration;
segmentDuration = (endTime - HitObject.StartTime) / repeatCount;
segmentDuration = (endTime - HitObject.StartTime) / spanCount;
}
public override Pattern Generate()
{
if (repeatCount > 1)
if (spanCount > 1)
{
if (segmentDuration <= 90)
return generateRandomHoldNotes(HitObject.StartTime, 1);
@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (segmentDuration <= 120)
{
convertType |= PatternType.ForceNotStack;
return generateRandomNotes(HitObject.StartTime, repeatCount + 1);
return generateRandomNotes(HitObject.StartTime, spanCount + 1);
}
if (segmentDuration <= 160)
@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (duration >= 4000)
return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0);
if (segmentDuration > 400 && repeatCount < TotalColumns - 1 - RandomStart)
if (segmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart)
return generateTiledHoldNotes(HitObject.StartTime);
return generateHoldAndNormalNotes(HitObject.StartTime);
@ -212,7 +212,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
bool increasing = Random.NextDouble() > 0.5;
for (int i = 0; i <= repeatCount; i++)
for (int i = 0; i <= spanCount; i++)
{
addToPattern(pattern, column, startTime, startTime);
startTime += segmentDuration;
@ -262,7 +262,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int interval = Random.Next(1, TotalColumns - (legacy ? 1 : 0));
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
for (int i = 0; i <= repeatCount; i++)
for (int i = 0; i <= spanCount; i++)
{
addToPattern(pattern, nextColumn, startTime, startTime);
@ -350,7 +350,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
var pattern = new Pattern();
int columnRepeat = Math.Min(repeatCount, TotalColumns);
int columnRepeat = Math.Min(spanCount, TotalColumns);
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns)
@ -409,7 +409,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
int nextColumn = Random.Next(RandomStart, TotalColumns);
var rowPattern = new Pattern();
for (int i = 0; i <= repeatCount; i++)
for (int i = 0; i <= spanCount; i++)
{
if (!(ignoreHead && startTime == HitObject.StartTime))
{
@ -442,7 +442,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (curveData == null)
return HitObject.Samples;
double segmentTime = (endTime - HitObject.StartTime) / repeatCount;
double segmentTime = (endTime - HitObject.StartTime) / spanCount;
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
return curveData.RepeatSamples[index];

View File

@ -72,6 +72,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
}
public void UpdateSnakingPosition(Vector2 start, Vector2 end) => Position = repeatPoint.RepeatIndex % 2 == 1 ? end : start;
public void UpdateSnakingPosition(Vector2 start, Vector2 end) => Position = repeatPoint.RepeatIndex % 2 == 0 ? end : start;
}
}

View File

@ -10,6 +10,7 @@ using System.Linq;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
@ -72,12 +73,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AddNested(InitialCircle);
var repeatDuration = s.Curve.Distance / s.Velocity;
var spanDuration = s.Curve.Distance / s.Velocity;
foreach (var tick in s.NestedHitObjects.OfType<SliderTick>())
{
var repeatStartTime = s.StartTime + tick.RepeatIndex * repeatDuration;
var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? HitObject.TimeFadein : HitObject.TimeFadein / 2);
var fadeOutTime = repeatStartTime + repeatDuration;
var spanStartTime = s.StartTime + tick.SpanIndex * spanDuration;
var fadeInTime = spanStartTime + (tick.StartTime - spanStartTime) / 2 - (tick.SpanIndex == 0 ? HitObject.TimeFadein : HitObject.TimeFadein / 2);
var fadeOutTime = spanStartTime + spanDuration;
var drawableTick = new DrawableSliderTick(tick)
{
@ -92,9 +93,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
foreach (var repeatPoint in s.NestedHitObjects.OfType<RepeatPoint>())
{
var repeatStartTime = s.StartTime + repeatPoint.RepeatIndex * repeatDuration;
var repeatStartTime = s.StartTime + (repeatPoint.RepeatIndex + 1) * spanDuration;
var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? HitObject.TimeFadein : HitObject.TimeFadein / 2);
var fadeOutTime = repeatStartTime + repeatDuration;
var fadeOutTime = repeatStartTime + spanDuration;
var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this)
{
@ -109,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
}
private int currentRepeat;
private int currentSpan;
public bool Tracking;
protected override void Update()
@ -120,17 +121,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
double progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
int repeat = slider.RepeatAt(progress);
int span = slider.SpanAt(progress);
progress = slider.ProgressAt(progress);
if (repeat > currentRepeat)
currentRepeat = repeat;
if (span > currentSpan)
currentSpan = span;
//todo: we probably want to reconsider this before adding scoring, but it looks and feels nice.
if (!InitialCircle.Judgements.Any(j => j.IsHit))
InitialCircle.Position = slider.Curve.PositionAt(progress);
foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(progress, repeat);
foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(progress, span);
foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0));
foreach (var t in ticks.Children) t.Tracking = Ball.Tracking;
}

View File

@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}
}
public void UpdateProgress(double progress, int repeat)
public void UpdateProgress(double progress, int span)
{
Position = slider.Curve.PositionAt(progress);
}

View File

@ -15,6 +15,7 @@ using OpenTK;
using OpenTK.Graphics.ES30;
using OpenTK.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
@ -164,14 +165,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
return true;
}
public void UpdateProgress(double progress, int repeat)
public void UpdateProgress(double progress, int span)
{
double start = 0;
double end = snakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1;
if (repeat >= slider.RepeatCount - 1)
if (span >= slider.SpanCount() - 1)
{
if (Math.Min(repeat, slider.RepeatCount - 1) % 2 == 1)
if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1)
{
start = 0;
end = snakingOut ? progress : 1;

View File

@ -5,6 +5,6 @@ namespace osu.Game.Rulesets.Osu.Objects
{
public interface ISliderProgress
{
void UpdateProgress(double progress, int repeat);
void UpdateProgress(double progress, int span);
}
}

View File

@ -20,12 +20,12 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary>
private const float base_scoring_distance = 100;
public readonly SliderCurve Curve = new SliderCurve();
public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity;
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
public double Duration => EndTime - StartTime;
public override Vector2 EndPosition => PositionAt(1);
public override Vector2 EndPosition => this.PositionAt(1);
public SliderCurve Curve { get; } = new SliderCurve();
public List<Vector2> ControlPoints
{
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Objects
internal float LazyTravelDistance;
public List<List<SampleInfo>> RepeatSamples { get; set; } = new List<List<SampleInfo>>();
public int RepeatCount { get; set; } = 1;
public int RepeatCount { get; set; }
private int stackHeight;
@ -88,18 +88,6 @@ namespace osu.Game.Rulesets.Osu.Objects
TickDistance = scoringDistance / difficulty.SliderTickRate;
}
public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress));
public double ProgressAt(double progress)
{
double p = progress * RepeatCount % 1;
if (RepeatAt(progress) % 2 == 1)
p = 1 - p;
return p;
}
public int RepeatAt(double progress) => (int)(progress * RepeatCount);
protected override void CreateNestedHitObjects()
{
base.CreateNestedHitObjects();
@ -114,14 +102,14 @@ namespace osu.Game.Rulesets.Osu.Objects
var length = Curve.Distance;
var tickDistance = Math.Min(TickDistance, length);
var repeatDuration = length / Velocity;
var spanDuration = length / Velocity;
var minDistanceFromEnd = Velocity * 0.01;
for (var repeat = 0; repeat < RepeatCount; repeat++)
for (var span = 0; span < this.SpanCount(); span++)
{
var repeatStartTime = StartTime + repeat * repeatDuration;
var reversed = repeat % 2 == 1;
var spanStartTime = StartTime + span * spanDuration;
var reversed = span % 2 == 1;
for (var d = tickDistance; d <= length; d += tickDistance)
{
@ -144,8 +132,8 @@ namespace osu.Game.Rulesets.Osu.Objects
AddNested(new SliderTick
{
RepeatIndex = repeat,
StartTime = repeatStartTime + timeProgress * repeatDuration,
SpanIndex = span,
StartTime = spanStartTime + timeProgress * spanDuration,
Position = Curve.PositionAt(distanceProgress),
StackHeight = StackHeight,
Scale = Scale,
@ -160,19 +148,19 @@ namespace osu.Game.Rulesets.Osu.Objects
{
var repeatDuration = Distance / Velocity;
for (var repeat = 1; repeat < RepeatCount; repeat++)
for (int repeatIndex = 0, repeat = 1; repeatIndex < RepeatCount; repeatIndex++, repeat++)
{
var repeatStartTime = StartTime + repeat * repeatDuration;
AddNested(new RepeatPoint
{
RepeatIndex = repeat,
RepeatIndex = repeatIndex,
StartTime = repeatStartTime,
Position = Curve.PositionAt(repeat % 2),
StackHeight = StackHeight,
Scale = Scale,
ComboColour = ComboColour,
Samples = new List<SampleInfo>(RepeatSamples[repeat])
Samples = new List<SampleInfo>(RepeatSamples[repeatIndex])
});
}
}

View File

@ -5,6 +5,6 @@ namespace osu.Game.Rulesets.Osu.Objects
{
public class SliderTick : OsuHitObject
{
public int RepeatIndex { get; set; }
public int SpanIndex { get; set; }
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Linq;
using osu.Game.Rulesets.Objects.Types;
using OpenTK;
using osu.Game.Rulesets.Osu.Objects;

View File

@ -84,14 +84,9 @@ namespace osu.Game.Rulesets.Osu.Tests
private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2)
{
repeats++; // The first run through the slider is considered a repeat
var repeatSamples = new List<List<SampleInfo>>();
if (repeats > 1)
{
for (int i = 0; i < repeats; i++)
repeatSamples.Add(new List<SampleInfo>());
}
for (int i = 0; i < repeats; i++)
repeatSamples.Add(new List<SampleInfo>());
var slider = new Slider
{

View File

@ -84,7 +84,8 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
if (distanceData != null)
{
int repeats = repeatsData?.RepeatCount ?? 1;
// Number of spans of the object - one for the initial length and for each repeat
int spans = repeatsData?.SpanCount() ?? 1;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
@ -93,7 +94,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment;
// The true distance, accounting for any repeats. This ends up being the drum roll distance later
double distance = distanceData.Distance * repeats * legacy_velocity_multiplier;
double distance = distanceData.Distance * spans * legacy_velocity_multiplier;
// The velocity of the taiko hit object - calculated as the velocity of a drum roll
double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
@ -111,7 +112,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
double osuDuration = distance / osuVelocity;
// If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / repeats);
double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans);
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
{

View File

@ -77,6 +77,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
if (repeatCount > 9000)
throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high");
// osu-stable treated the first span of the slider as a repeat, but no repeats are happening
repeatCount = Math.Max(0, repeatCount - 1);
if (split.Length > 7)
length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture);
@ -84,8 +88,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
readCustomSampleBanks(split[10], bankInfo);
// One node for each repeat + the start and end nodes
// Note that the first length of the slider is considered a repeat, but there are no actual repeats happening
int nodes = Math.Max(0, repeatCount - 1) + 2;
int nodes = repeatCount + 2;
// Populate node sample bank infos with the default hit object sample bank
var nodeBankInfos = new List<SampleBankInfo>();
@ -128,7 +131,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
// Generate the final per-node samples
var nodeSamples = new List<List<SampleInfo>>(nodes);
for (int i = 0; i <= repeatCount; i++)
for (int i = 0; i < nodes; i++)
nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i]));
result = CreateSlider(new Vector2(int.Parse(split[0]), int.Parse(split[1])), combo, points, length, curveType, repeatCount, nodeSamples);

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Objects.Types;
using System;
using System.Collections.Generic;
using OpenTK;
using osu.Game.Audio;
@ -18,34 +17,23 @@ namespace osu.Game.Rulesets.Objects.Legacy
/// </summary>
private const float base_scoring_distance = 100;
/// <summary>
/// <see cref="ConvertSlider"/>s don't need a curve since they're converted to ruleset-specific hitobjects.
/// </summary>
public SliderCurve Curve { get; } = null;
public List<Vector2> ControlPoints { get; set; }
public CurveType CurveType { get; set; }
public double Distance { get; set; }
public List<List<SampleInfo>> RepeatSamples { get; set; }
public int RepeatCount { get; set; } = 1;
public int RepeatCount { get; set; }
public double EndTime => StartTime + RepeatCount * Distance / Velocity;
public double EndTime => StartTime + this.SpanCount() * Distance / Velocity;
public double Duration => EndTime - StartTime;
public double Velocity = 1;
public Vector2 PositionAt(double progress)
{
throw new NotImplementedException();
}
public double ProgressAt(double progress)
{
throw new NotImplementedException();
}
public int RepeatAt(double progress)
{
throw new NotImplementedException();
}
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);

View File

@ -11,6 +11,11 @@ namespace osu.Game.Rulesets.Objects.Types
/// </summary>
public interface IHasCurve : IHasDistance, IHasRepeats
{
/// <summary>
/// The curve.
/// </summary>
SliderCurve Curve { get; }
/// <summary>
/// The control points that shape the curve.
/// </summary>
@ -20,7 +25,10 @@ namespace osu.Game.Rulesets.Objects.Types
/// The type of curve.
/// </summary>
CurveType CurveType { get; }
}
public static class HasCurveExtensions
{
/// <summary>
/// Computes the position on the curve at a given progress, accounting for repeat logic.
/// <para>
@ -28,20 +36,28 @@ namespace osu.Game.Rulesets.Objects.Types
/// </para>
/// </summary>
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param>
Vector2 PositionAt(double progress);
public static Vector2 PositionAt(this IHasCurve obj, double progress)
=> obj.Curve.PositionAt(obj.ProgressAt(progress));
/// <summary>
/// Finds the progress along the curve, accounting for repeat logic.
/// </summary>
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param>
/// <returns>[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</returns>
double ProgressAt(double progress);
public static double ProgressAt(this IHasCurve obj, double progress)
{
double p = progress * obj.SpanCount() % 1;
if (obj.SpanAt(progress) % 2 == 1)
p = 1 - p;
return p;
}
/// <summary>
/// Determines which repeat of the curve the progress point is on.
/// Determines which span of the curve the progress point is on.
/// </summary>
/// <param name="progress">[0, 1] where 0 is the beginning of the curve and 1 is the end of the curve.</param>
/// <returns>[0, RepeatCount] where 0 is the first run.</returns>
int RepeatAt(double progress);
/// <returns>[0, SpanCount) where 0 is the first run.</returns>
public static int SpanAt(this IHasCurve obj, double progress)
=> (int)(progress * obj.SpanCount());
}
}

View File

@ -21,4 +21,13 @@ namespace osu.Game.Rulesets.Objects.Types
/// </summary>
List<List<SampleInfo>> RepeatSamples { get; }
}
public static class HasRepeatsExtensions
{
/// <summary>
/// The amount of times the length of this <see cref="IHasRepeats"/> spans.
/// </summary>
/// <param name="obj">The object that has repeats.</param>
public static int SpanCount(this IHasRepeats obj) => obj.RepeatCount + 1;
}
}