Merge pull request #21567 from apollo-dw/reflect-fix

Reflect nested objects in the playfield correctly
This commit is contained in:
Dean Herbert 2022-12-08 12:38:05 +09:00 committed by GitHub
commit 6a73b98d5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 117 additions and 22 deletions

View File

@ -0,0 +1,91 @@
// 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.Linq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
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.Tests
{
[TestFixture]
public class OsuHitObjectGenerationUtilsTest
{
private static Slider createTestSlider()
{
var slider = new Slider
{
Position = new Vector2(128, 128),
Path = new SliderPath
{
ControlPoints =
{
new PathControlPoint(new Vector2(), PathType.Linear),
new PathControlPoint(new Vector2(-64, -128), PathType.Linear), // absolute position: (64, 0)
new PathControlPoint(new Vector2(-128, 0), PathType.Linear) // absolute position: (0, 128)
}
},
RepeatCount = 1
};
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return slider;
}
[Test]
public void TestReflectSliderHorizontallyAlongPlayfield()
{
var slider = createTestSlider();
OsuHitObjectGenerationUtils.ReflectHorizontallyAlongPlayfield(slider);
Assert.That(slider.Position, Is.EqualTo(new Vector2(OsuPlayfield.BASE_SIZE.X - 128, 128)));
Assert.That(slider.NestedHitObjects.OfType<SliderRepeat>().Single().Position, Is.EqualTo(new Vector2(OsuPlayfield.BASE_SIZE.X - 0, 128)));
Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[]
{
new Vector2(),
new Vector2(64, -128),
new Vector2(128, 0)
}));
}
[Test]
public void TestReflectSliderVerticallyAlongPlayfield()
{
var slider = createTestSlider();
OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(slider);
Assert.That(slider.Position, Is.EqualTo(new Vector2(128, OsuPlayfield.BASE_SIZE.Y - 128)));
Assert.That(slider.NestedHitObjects.OfType<SliderRepeat>().Single().Position, Is.EqualTo(new Vector2(0, OsuPlayfield.BASE_SIZE.Y - 128)));
Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[]
{
new Vector2(),
new Vector2(-64, 128),
new Vector2(-128, 0)
}));
}
[Test]
public void TestFlipSliderInPlaceHorizontally()
{
var slider = createTestSlider();
OsuHitObjectGenerationUtils.FlipSliderInPlaceHorizontally(slider);
Assert.That(slider.Position, Is.EqualTo(new Vector2(128, 128)));
Assert.That(slider.NestedHitObjects.OfType<SliderRepeat>().Single().Position, Is.EqualTo(new Vector2(256, 128)));
Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[]
{
new Vector2(),
new Vector2(64, -128),
new Vector2(128, 0)
}));
}
}
}

View File

@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods
{
var osuObject = (OsuHitObject)hitObject;
OsuHitObjectGenerationUtils.ReflectVertically(osuObject);
OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(osuObject);
}
}
}

View File

@ -27,16 +27,16 @@ namespace osu.Game.Rulesets.Osu.Mods
switch (Reflection.Value)
{
case MirrorType.Horizontal:
OsuHitObjectGenerationUtils.ReflectHorizontally(osuObject);
OsuHitObjectGenerationUtils.ReflectHorizontallyAlongPlayfield(osuObject);
break;
case MirrorType.Vertical:
OsuHitObjectGenerationUtils.ReflectVertically(osuObject);
OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(osuObject);
break;
case MirrorType.Both:
OsuHitObjectGenerationUtils.ReflectHorizontally(osuObject);
OsuHitObjectGenerationUtils.ReflectVertically(osuObject);
OsuHitObjectGenerationUtils.ReflectHorizontallyAlongPlayfield(osuObject);
OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(osuObject);
break;
}
}

View File

@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Mods
if (positionInfos[i].HitObject is Slider slider && random.NextDouble() < 0.5)
{
OsuHitObjectGenerationUtils.FlipSliderHorizontally(slider);
OsuHitObjectGenerationUtils.FlipSliderInPlaceHorizontally(slider);
}
if (i == 0)

View File

@ -112,29 +112,44 @@ namespace osu.Game.Rulesets.Osu.Utils
/// Reflects the position of the <see cref="OsuHitObject"/> in the playfield horizontally.
/// </summary>
/// <param name="osuObject">The object to reflect.</param>
public static void ReflectHorizontally(OsuHitObject osuObject)
public static void ReflectHorizontallyAlongPlayfield(OsuHitObject osuObject)
{
osuObject.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - osuObject.X, osuObject.Position.Y);
if (osuObject is not Slider slider)
return;
FlipSliderHorizontally(slider);
void reflectNestedObject(OsuHitObject nested) => nested.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - nested.Position.X, nested.Position.Y);
static void reflectControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y);
modifySlider(slider, reflectNestedObject, reflectControlPoint);
}
/// <summary>
/// Reflects the position of the <see cref="OsuHitObject"/> in the playfield vertically.
/// </summary>
/// <param name="osuObject">The object to reflect.</param>
public static void ReflectVertically(OsuHitObject osuObject)
public static void ReflectVerticallyAlongPlayfield(OsuHitObject osuObject)
{
osuObject.Position = new Vector2(osuObject.Position.X, OsuPlayfield.BASE_SIZE.Y - osuObject.Y);
if (osuObject is not Slider slider)
return;
void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(nested.X, slider.Y - (nested.Y - slider.Y));
static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(point.Position.X, -point.Position.Y);
void reflectNestedObject(OsuHitObject nested) => nested.Position = new Vector2(nested.Position.X, OsuPlayfield.BASE_SIZE.Y - nested.Position.Y);
static void reflectControlPoint(PathControlPoint point) => point.Position = new Vector2(point.Position.X, -point.Position.Y);
modifySlider(slider, reflectNestedObject, reflectControlPoint);
}
/// <summary>
/// Flips the position of the <see cref="Slider"/> around its start position horizontally.
/// </summary>
/// <param name="slider">The slider to be flipped.</param>
public static void FlipSliderInPlaceHorizontally(Slider slider)
{
void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(slider.X - (nested.X - slider.X), nested.Y);
static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y);
modifySlider(slider, flipNestedObject, flipControlPoint);
}
@ -152,17 +167,6 @@ namespace osu.Game.Rulesets.Osu.Utils
modifySlider(slider, rotateNestedObject, rotateControlPoint);
}
/// <summary>
/// Flips the slider about its start position horizontally.
/// </summary>
public static void FlipSliderHorizontally(Slider slider)
{
void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(slider.X - (nested.X - slider.X), nested.Y);
static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y);
modifySlider(slider, flipNestedObject, flipControlPoint);
}
private static void modifySlider(Slider slider, Action<OsuHitObject> modifyNestedObject, Action<PathControlPoint> modifyControlPoint)
{
// No need to update the head and tail circles, since slider handles that when the new slider path is set