mirror of https://github.com/ppy/osu
112 lines
4.3 KiB
C#
112 lines
4.3 KiB
C#
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
// See the LICENCE file in the repository root for full licence text.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using osu.Game.Rulesets.Osu.Replays;
|
|
using osu.Game.Rulesets.Replays;
|
|
using osuTK;
|
|
|
|
namespace osu.Game.Rulesets.Osu.Tests
|
|
{
|
|
public class SpinFramesGenerator
|
|
{
|
|
/// <summary>
|
|
/// A small amount to spin beyond a given angle to mitigate floating-point precision errors.
|
|
/// </summary>
|
|
public const float SPIN_ERROR = MathF.PI / 8;
|
|
|
|
/// <summary>
|
|
/// The offset from the centre of the spinner at which to spin.
|
|
/// </summary>
|
|
private const float centre_spin_offset = 50;
|
|
|
|
private readonly double startTime;
|
|
private readonly float startAngle;
|
|
private readonly List<(float deltaAngle, double duration)> sequences = new List<(float deltaAngle, double duration)>();
|
|
|
|
/// <summary>
|
|
/// Creates a new <see cref="SpinFramesGenerator"/> that can be used to generate spinner spin frames.
|
|
/// </summary>
|
|
/// <param name="startTime">The time at which to start spinning.</param>
|
|
/// <param name="startAngle">The angle, in radians, at which to start spinning from. Defaults to the positive-y-axis.</param>
|
|
public SpinFramesGenerator(double startTime, float startAngle = -MathF.PI / 2f)
|
|
{
|
|
this.startTime = startTime;
|
|
this.startAngle = startAngle;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Performs a single spin.
|
|
/// </summary>
|
|
/// <param name="delta">The amount of degrees to spin.</param>
|
|
/// <param name="duration">The time to spend to perform the spin.</param>
|
|
/// <returns>This <see cref="SpinFramesGenerator"/>.</returns>
|
|
public SpinFramesGenerator Spin(float delta, double duration)
|
|
{
|
|
sequences.Add((delta / 360 * 2 * MathF.PI, duration));
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs the replay frames.
|
|
/// </summary>
|
|
/// <returns>The replay frames.</returns>
|
|
public List<ReplayFrame> Build()
|
|
{
|
|
List<ReplayFrame> frames = new List<ReplayFrame>();
|
|
|
|
double lastTime = startTime;
|
|
float lastAngle = startAngle;
|
|
int lastDirection = 0;
|
|
|
|
for (int i = 0; i < sequences.Count; i++)
|
|
{
|
|
var seq = sequences[i];
|
|
|
|
int seqDirection = Math.Sign(seq.deltaAngle);
|
|
float seqError = SPIN_ERROR * seqDirection;
|
|
|
|
if (seqDirection == lastDirection)
|
|
{
|
|
// Spinning in the same direction, but the error was already added in the last rotation.
|
|
seqError = 0;
|
|
}
|
|
else if (lastDirection != 0)
|
|
{
|
|
// Spinning in a different direction, we need to account for the error of the start angle, so double it.
|
|
seqError *= 2;
|
|
}
|
|
|
|
double seqStartTime = lastTime;
|
|
double seqEndTime = lastTime + seq.duration;
|
|
float seqStartAngle = lastAngle;
|
|
float seqEndAngle = seqStartAngle + seq.deltaAngle + seqError;
|
|
|
|
// Intermediate spin frames.
|
|
for (; lastTime < seqEndTime; lastTime += 10)
|
|
frames.Add(new OsuReplayFrame(lastTime, calcOffsetAt((lastTime - seqStartTime) / (seqEndTime - seqStartTime), seqStartAngle, seqEndAngle), OsuAction.LeftButton));
|
|
|
|
// Final frame at the end of the current spin.
|
|
frames.Add(new OsuReplayFrame(seqEndTime, calcOffsetAt(1, seqStartAngle, seqEndAngle), OsuAction.LeftButton));
|
|
|
|
lastTime = seqEndTime;
|
|
lastAngle = seqEndAngle;
|
|
lastDirection = seqDirection;
|
|
}
|
|
|
|
// Key release frame.
|
|
if (frames.Count > 0)
|
|
frames.Add(new OsuReplayFrame(frames[^1].Time, ((OsuReplayFrame)frames[^1]).Position));
|
|
|
|
return frames;
|
|
}
|
|
|
|
private static Vector2 calcOffsetAt(double p, float startAngle, float endAngle)
|
|
{
|
|
float angle = startAngle + (endAngle - startAngle) * (float)p;
|
|
return new Vector2(256, 192) + centre_spin_offset * new Vector2(MathF.Cos(angle), MathF.Sin(angle));
|
|
}
|
|
}
|
|
}
|