From 1776485b93eeb2ff0b7e5ccf1657aa5704dac997 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 7 Dec 2022 20:20:11 +0000 Subject: [PATCH 1/4] Reflect nested objects vertically in the playfield correctly --- osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index f565456911..5b327fc3b0 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu.Utils if (osuObject is not Slider slider) return; - void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(nested.X, slider.Y - (nested.Y - slider.Y)); + void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(nested.Position.X, OsuPlayfield.BASE_SIZE.Y - nested.Position.Y); static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(point.Position.X, -point.Position.Y); modifySlider(slider, flipNestedObject, flipControlPoint); From 684b16cef5dc7338ef3333106e3433de8085153f Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 7 Dec 2022 21:09:53 +0000 Subject: [PATCH 2/4] Disambiguate object flipping and reflection methods --- osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs | 8 ++--- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 2 +- .../Utils/OsuHitObjectGenerationUtils.cs | 32 +++++++++++-------- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index 5430929143..19d4a1bf83 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods { var osuObject = (OsuHitObject)hitObject; - OsuHitObjectGenerationUtils.ReflectVertically(osuObject); + OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(osuObject); } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs index 0a54d58718..6d01808fb5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs @@ -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; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 58f5b2fa8d..307d731fd4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -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) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 5b327fc3b0..15bc03261f 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -112,21 +112,24 @@ namespace osu.Game.Rulesets.Osu.Utils /// Reflects the position of the in the playfield horizontally. /// /// The object to reflect. - 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 flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - nested.Position.X, nested.Position.Y); + static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y); + + modifySlider(slider, flipNestedObject, flipControlPoint); } /// /// Reflects the position of the in the playfield vertically. /// /// The object to reflect. - 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); @@ -139,6 +142,18 @@ namespace osu.Game.Rulesets.Osu.Utils modifySlider(slider, flipNestedObject, flipControlPoint); } + /// + /// Flips the position of the around its start position horizontally. + /// + /// The slider to be flipped. + 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); + } + /// /// Rotate a slider about its start position by the specified angle. /// @@ -152,17 +167,6 @@ namespace osu.Game.Rulesets.Osu.Utils modifySlider(slider, rotateNestedObject, rotateControlPoint); } - /// - /// Flips the slider about its start position horizontally. - /// - 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 modifyNestedObject, Action modifyControlPoint) { // No need to update the head and tail circles, since slider handles that when the new slider path is set From 7676838cc04d162c37d87749d275b582cbed8e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Dec 2022 23:27:02 +0100 Subject: [PATCH 3/4] Apply "reflect" vernacular in nested methods --- .../Utils/OsuHitObjectGenerationUtils.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 15bc03261f..aa4cd0af14 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -119,10 +119,10 @@ namespace osu.Game.Rulesets.Osu.Utils if (osuObject is not Slider slider) return; - void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - nested.Position.X, nested.Position.Y); - static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y); + 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, flipNestedObject, flipControlPoint); + modifySlider(slider, reflectNestedObject, reflectControlPoint); } /// @@ -136,10 +136,10 @@ namespace osu.Game.Rulesets.Osu.Utils if (osuObject is not Slider slider) return; - void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(nested.Position.X, OsuPlayfield.BASE_SIZE.Y - nested.Position.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, flipNestedObject, flipControlPoint); + modifySlider(slider, reflectNestedObject, reflectControlPoint); } /// From 5807d4c43dcc2dfd7f8b9b61e277b2dc35c862da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Dec 2022 23:50:01 +0100 Subject: [PATCH 4/4] Add test coverage for slider transformations --- .../OsuHitObjectGenerationUtilsTest.cs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs diff --git a/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs new file mode 100644 index 0000000000..daa914cac2 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs @@ -0,0 +1,91 @@ +// Copyright (c) ppy Pty Ltd . 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().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().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().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) + })); + } + } +}