From 6301f837e0b17a8216f2af8a3ee55e064249b16d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Oct 2019 15:27:23 +0900 Subject: [PATCH 1/8] Initial implementation of osu! beat snapping grid --- .../TestSceneOsuBeatSnapGrid.cs | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs new file mode 100644 index 0000000000..9f0d59afab --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs @@ -0,0 +1,170 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneOsuBeatSnapGrid : ManualInputManagerTestScene + { + private const double beat_length = 100; + private static readonly Vector2 grid_position = new Vector2(512, 384); + + [Cached(typeof(IEditorBeatmap))] + private readonly EditorBeatmap editorBeatmap; + + [Cached] + private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); + + private OsuBeatSnapGrid grid; + private Drawable cursor; + + public TestSceneOsuBeatSnapGrid() + { + editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + } + + [SetUp] + public void Setup() => Schedule(() => + { + Clear(); + + editorBeatmap.ControlPointInfo.TimingPoints.Clear(); + editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); + + beatDivisor.Value = 1; + }); + + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + if (cursor != null) + cursor.Position = grid?.GetSnapPosition(grid.ToLocalSpace(e.ScreenSpaceMousePosition)) ?? e.ScreenSpaceMousePosition; + + return true; + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + [TestCase(4)] + [TestCase(6)] + [TestCase(8)] + [TestCase(12)] + [TestCase(16)] + public void TestBeatDivisor(int divisor) + { + AddStep($"set beat divisor = {divisor}", () => beatDivisor.Value = divisor); + createGrid(); + } + + private void createGrid() + { + AddStep("create grid", () => + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.SlateGray + }, + grid = new OsuBeatSnapGrid(new HitCircle { Position = grid_position }), + cursor = new Circle + { + Origin = Anchor.Centre, + Size = new Vector2(50), + Colour = Color4.Red + } + }; + }); + } + + private abstract class CircularBeatSnapGrid : BeatSnapGrid + { + protected override void CreateGrid(Vector2 startPosition) + { + float maxDistance = Math.Max( + Vector2.Distance(startPosition, Vector2.Zero), + Math.Max( + Vector2.Distance(startPosition, new Vector2(DrawWidth, 0)), + Math.Max( + Vector2.Distance(startPosition, new Vector2(0, DrawHeight)), + Vector2.Distance(startPosition, DrawSize)))); + + int requiredCircles = (int)(maxDistance / DistanceSpacing); + + for (int i = 0; i < requiredCircles; i++) + { + float radius = (i + 1) * DistanceSpacing * 2; + + AddInternal(new CircularProgress + { + Origin = Anchor.Centre, + Position = startPosition, + Current = { Value = 1 }, + Size = new Vector2(radius), + InnerRadius = 4 * 1f / radius, + Colour = GetColourForBeatIndex(i) + }); + } + } + + public override Vector2 GetSnapPosition(Vector2 position) + { + Vector2 direction = position - StartPosition; + float distance = direction.Length; + + float radius = DistanceSpacing; + int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); + + if (radialCount <= 0) + return position; + + Vector2 normalisedDirection = direction * new Vector2(1f / distance); + + return StartPosition + normalisedDirection * radialCount * radius; + } + } + + private class OsuBeatSnapGrid : CircularBeatSnapGrid + { + /// + /// Scoring distance with a speed-adjusted beat length of 1 second. + /// + private const float base_scoring_distance = 100; + + public OsuBeatSnapGrid(OsuHitObject hitObject) + : base(hitObject, hitObject.StackedEndPosition) + { + } + + protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time); + DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time); + + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + + return (float)(scoringDistance / timingPoint.BeatLength); + } + } + } +} From 4d32a8aa6b5cca76f5b993791ce4e536a4709836 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Oct 2019 17:11:37 +0900 Subject: [PATCH 2/8] More tests --- .../TestSceneOsuBeatSnapGrid.cs | 172 +++++++++++++++--- 1 file changed, 145 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs index 9f0d59afab..7baa2f0d72 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs @@ -5,9 +5,11 @@ using System; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; @@ -32,12 +34,13 @@ namespace osu.Game.Rulesets.Osu.Tests [Cached] private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - private OsuBeatSnapGrid grid; - private Drawable cursor; + private TestOsuBeatSnapGrid grid; public TestSceneOsuBeatSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + + createGrid(); } [SetUp] @@ -45,22 +48,14 @@ namespace osu.Game.Rulesets.Osu.Tests { Clear(); + editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; + editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); editorBeatmap.ControlPointInfo.TimingPoints.Clear(); editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); beatDivisor.Value = 1; }); - protected override bool OnMouseMove(MouseMoveEvent e) - { - base.OnMouseMove(e); - - if (cursor != null) - cursor.Position = grid?.GetSnapPosition(grid.ToLocalSpace(e.ScreenSpaceMousePosition)) ?? e.ScreenSpaceMousePosition; - - return true; - } - [TestCase(1)] [TestCase(2)] [TestCase(3)] @@ -75,6 +70,80 @@ namespace osu.Game.Rulesets.Osu.Tests createGrid(); } + [TestCase(100, 100)] + [TestCase(200, 100)] + public void TestBeatLength(float beatLength, float expectedSpacing) + { + AddStep($"set beat length = {beatLength}", () => + { + editorBeatmap.ControlPointInfo.TimingPoints.Clear(); + editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength }); + }); + + createGrid(); + AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing)); + } + + [TestCase(0.5f, 50)] + [TestCase(1, 100)] + [TestCase(1.5f, 150)] + public void TestSpeedMultiplier(float multiplier, float expectedSpacing) + { + AddStep($"set speed multiplier = {multiplier}", () => + { + editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); + editorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = multiplier }); + }); + + createGrid(); + AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing)); + } + + [TestCase(0.5f, 50)] + [TestCase(1, 100)] + [TestCase(1.5f, 150)] + public void TestSliderMultiplier(float multiplier, float expectedSpacing) + { + AddStep($"set speed multiplier = {multiplier}", () => editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = multiplier); + createGrid(); + AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing)); + } + + [Test] + public void TestCursorInCentre() + { + createGrid(); + + AddStep("move mouse to centre", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position))); + assertSnappedDistance((float)beat_length); + } + + [Test] + public void TestCursorBeforeMovementPoint() + { + createGrid(); + + AddStep("move mouse to just before movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.49f))); + assertSnappedDistance((float)beat_length); + } + + [Test] + public void TestCursorAfterMovementPoint() + { + createGrid(); + + AddStep("move mouse to just after movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.51f))); + assertSnappedDistance((float)beat_length * 2); + } + + private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () => + { + Vector2 snappedPosition = grid.GetSnapPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)); + float distance = Vector2.Distance(snappedPosition, grid_position); + + return Precision.AlmostEquals(expectedDistance, distance); + }); + private void createGrid() { AddStep("create grid", () => @@ -86,28 +155,77 @@ namespace osu.Game.Rulesets.Osu.Tests RelativeSizeAxes = Axes.Both, Colour = Color4.SlateGray }, - grid = new OsuBeatSnapGrid(new HitCircle { Position = grid_position }), - cursor = new Circle - { - Origin = Anchor.Centre, - Size = new Vector2(50), - Colour = Color4.Red - } + grid = new TestOsuBeatSnapGrid(new HitCircle { Position = grid_position }), + new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnapPosition(grid.ToLocalSpace(v)) } }; }); } + private class SnappingCursorContainer : CompositeDrawable + { + public Func GetSnapPosition; + + private readonly Drawable cursor; + + public SnappingCursorContainer() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = cursor = new Circle + { + Origin = Anchor.Centre, + Size = new Vector2(50), + Colour = Color4.Red + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updatePosition(GetContainingInputManager().CurrentState.Mouse.Position); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + updatePosition(e.ScreenSpaceMousePosition); + return true; + } + + private void updatePosition(Vector2 screenSpacePosition) + { + cursor.Position = GetSnapPosition.Invoke(screenSpacePosition); + } + } + + private class TestOsuBeatSnapGrid : OsuBeatSnapGrid + { + public new float DistanceSpacing => base.DistanceSpacing; + + public TestOsuBeatSnapGrid(OsuHitObject hitObject) + : base(hitObject) + { + } + } + private abstract class CircularBeatSnapGrid : BeatSnapGrid { - protected override void CreateGrid(Vector2 startPosition) + protected CircularBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) + : base(hitObject, centrePosition) + { + } + + protected override void CreateContent(Vector2 centrePosition) { float maxDistance = Math.Max( - Vector2.Distance(startPosition, Vector2.Zero), + Vector2.Distance(centrePosition, Vector2.Zero), Math.Max( - Vector2.Distance(startPosition, new Vector2(DrawWidth, 0)), + Vector2.Distance(centrePosition, new Vector2(DrawWidth, 0)), Math.Max( - Vector2.Distance(startPosition, new Vector2(0, DrawHeight)), - Vector2.Distance(startPosition, DrawSize)))); + Vector2.Distance(centrePosition, new Vector2(0, DrawHeight)), + Vector2.Distance(centrePosition, DrawSize)))); int requiredCircles = (int)(maxDistance / DistanceSpacing); @@ -118,7 +236,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddInternal(new CircularProgress { Origin = Anchor.Centre, - Position = startPosition, + Position = centrePosition, Current = { Value = 1 }, Size = new Vector2(radius), InnerRadius = 4 * 1f / radius, @@ -129,7 +247,7 @@ namespace osu.Game.Rulesets.Osu.Tests public override Vector2 GetSnapPosition(Vector2 position) { - Vector2 direction = position - StartPosition; + Vector2 direction = position - CentrePosition; float distance = direction.Length; float radius = DistanceSpacing; @@ -140,7 +258,7 @@ namespace osu.Game.Rulesets.Osu.Tests Vector2 normalisedDirection = direction * new Vector2(1f / distance); - return StartPosition + normalisedDirection * radialCount * radius; + return CentrePosition + normalisedDirection * radialCount * radius; } } From 45835f97a177597fb236793116382f5b8c7918ea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 11 Oct 2019 17:13:28 +0900 Subject: [PATCH 3/8] Split out grids into separate files --- .../TestSceneOsuBeatSnapGrid.cs | 80 +------------------ osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs | 33 ++++++++ .../Components/CircularBeatSnapGrid.cs | 63 +++++++++++++++ 3 files changed, 97 insertions(+), 79 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs create mode 100644 osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs index 7baa2f0d72..7399f12372 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs @@ -7,16 +7,13 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.MathUtils; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; -using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Tests.Visual; using osuTK; using osuTK.Graphics; @@ -209,80 +206,5 @@ namespace osu.Game.Rulesets.Osu.Tests { } } - - private abstract class CircularBeatSnapGrid : BeatSnapGrid - { - protected CircularBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) - : base(hitObject, centrePosition) - { - } - - protected override void CreateContent(Vector2 centrePosition) - { - float maxDistance = Math.Max( - Vector2.Distance(centrePosition, Vector2.Zero), - Math.Max( - Vector2.Distance(centrePosition, new Vector2(DrawWidth, 0)), - Math.Max( - Vector2.Distance(centrePosition, new Vector2(0, DrawHeight)), - Vector2.Distance(centrePosition, DrawSize)))); - - int requiredCircles = (int)(maxDistance / DistanceSpacing); - - for (int i = 0; i < requiredCircles; i++) - { - float radius = (i + 1) * DistanceSpacing * 2; - - AddInternal(new CircularProgress - { - Origin = Anchor.Centre, - Position = centrePosition, - Current = { Value = 1 }, - Size = new Vector2(radius), - InnerRadius = 4 * 1f / radius, - Colour = GetColourForBeatIndex(i) - }); - } - } - - public override Vector2 GetSnapPosition(Vector2 position) - { - Vector2 direction = position - CentrePosition; - float distance = direction.Length; - - float radius = DistanceSpacing; - int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); - - if (radialCount <= 0) - return position; - - Vector2 normalisedDirection = direction * new Vector2(1f / distance); - - return CentrePosition + normalisedDirection * radialCount * radius; - } - } - - private class OsuBeatSnapGrid : CircularBeatSnapGrid - { - /// - /// Scoring distance with a speed-adjusted beat length of 1 second. - /// - private const float base_scoring_distance = 100; - - public OsuBeatSnapGrid(OsuHitObject hitObject) - : base(hitObject, hitObject.StackedEndPosition) - { - } - - protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) - { - TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time); - DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time); - - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; - - return (float)(scoringDistance / timingPoint.BeatLength); - } - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs new file mode 100644 index 0000000000..d453e3d062 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuBeatSnapGrid : CircularBeatSnapGrid + { + /// + /// Scoring distance with a speed-adjusted beat length of 1 second. + /// + private const float base_scoring_distance = 100; + + public OsuBeatSnapGrid(OsuHitObject hitObject) + : base(hitObject, hitObject.StackedEndPosition) + { + } + + protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + { + TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time); + DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time); + + double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + + return (float)(scoringDistance / timingPoint.BeatLength); + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs new file mode 100644 index 0000000000..8492771808 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs @@ -0,0 +1,63 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Rulesets.Objects; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + public abstract class CircularBeatSnapGrid : BeatSnapGrid + { + protected CircularBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) + : base(hitObject, centrePosition) + { + } + + protected override void CreateContent(Vector2 centrePosition) + { + float maxDistance = Math.Max( + Vector2.Distance(centrePosition, Vector2.Zero), + Math.Max( + Vector2.Distance(centrePosition, new Vector2(DrawWidth, 0)), + Math.Max( + Vector2.Distance(centrePosition, new Vector2(0, DrawHeight)), + Vector2.Distance(centrePosition, DrawSize)))); + + int requiredCircles = (int)(maxDistance / DistanceSpacing); + + for (int i = 0; i < requiredCircles; i++) + { + float radius = (i + 1) * DistanceSpacing * 2; + + AddInternal(new CircularProgress + { + Origin = Anchor.Centre, + Position = centrePosition, + Current = { Value = 1 }, + Size = new Vector2(radius), + InnerRadius = 4 * 1f / radius, + Colour = GetColourForBeatIndex(i) + }); + } + } + + public override Vector2 GetSnapPosition(Vector2 position) + { + Vector2 direction = position - CentrePosition; + float distance = direction.Length; + + float radius = DistanceSpacing; + int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); + + if (radialCount <= 0) + return position; + + Vector2 normalisedDirection = direction * new Vector2(1f / distance); + + return CentrePosition + normalisedDirection * radialCount * radius; + } + } +} From 5ac5e34f8585fc0910ef4d6cf81307ca90e51b47 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 19:32:45 +0900 Subject: [PATCH 4/8] Use a cleaner distance function --- .../Edit/Compose/Components/CircularBeatSnapGrid.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs index 8492771808..bf363aeb37 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs @@ -18,13 +18,9 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void CreateContent(Vector2 centrePosition) { - float maxDistance = Math.Max( - Vector2.Distance(centrePosition, Vector2.Zero), - Math.Max( - Vector2.Distance(centrePosition, new Vector2(DrawWidth, 0)), - Math.Max( - Vector2.Distance(centrePosition, new Vector2(0, DrawHeight)), - Vector2.Distance(centrePosition, DrawSize)))); + float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X); + float dy = Math.Max(centrePosition.Y, DrawHeight - centrePosition.Y); + float maxDistance = new Vector2(dx, dy).Length; int requiredCircles = (int)(maxDistance / DistanceSpacing); From 2d4b7dc361ca061fd74cef1654ab86875c58d518 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 19:33:18 +0900 Subject: [PATCH 5/8] Remove redundant code --- .../Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs index bf363aeb37..09679f0553 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs @@ -48,11 +48,7 @@ namespace osu.Game.Screens.Edit.Compose.Components float radius = DistanceSpacing; int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); - if (radialCount <= 0) - return position; - Vector2 normalisedDirection = direction * new Vector2(1f / distance); - return CentrePosition + normalisedDirection * radialCount * radius; } } From b6b8098b989383b0402e2efe48476048f9b05f3f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 16 Oct 2019 19:44:53 +0900 Subject: [PATCH 6/8] Add an arbitrary offset to prevent div-by-0 --- .../Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs index 09679f0553..5e378f8393 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs @@ -43,6 +43,10 @@ namespace osu.Game.Screens.Edit.Compose.Components public override Vector2 GetSnapPosition(Vector2 position) { Vector2 direction = position - CentrePosition; + + if (direction == Vector2.Zero) + direction = new Vector2(0.001f, 0.001f); + float distance = direction.Length; float radius = DistanceSpacing; From f92331531c6e183a4644a423d20b6e9df17df2cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Oct 2019 15:32:02 +0900 Subject: [PATCH 7/8] Rename grid to DistanceSnap to be more in line with its purpose --- ...SnapGrid.cs => TestSceneOsuDistanceSnapGrid.cs} | 12 ++++++------ .../{OsuBeatSnapGrid.cs => OsuDistanceSnapGrid.cs} | 4 ++-- ...eatSnapGrid.cs => TestSceneDistanceSnapGrid.cs} | 14 +++++++------- ...BeatSnapGrid.cs => CircularDistanceSnapGrid.cs} | 4 ++-- .../{BeatSnapGrid.cs => DistanceSnapGrid.cs} | 13 ++++++++----- 5 files changed, 25 insertions(+), 22 deletions(-) rename osu.Game.Rulesets.Osu.Tests/{TestSceneOsuBeatSnapGrid.cs => TestSceneOsuDistanceSnapGrid.cs} (94%) rename osu.Game.Rulesets.Osu/Edit/{OsuBeatSnapGrid.cs => OsuDistanceSnapGrid.cs} (90%) rename osu.Game.Tests/Visual/Editor/{TestSceneBeatSnapGrid.cs => TestSceneDistanceSnapGrid.cs} (93%) rename osu.Game/Screens/Edit/Compose/Components/{CircularBeatSnapGrid.cs => CircularDistanceSnapGrid.cs} (92%) rename osu.Game/Screens/Edit/Compose/Components/{BeatSnapGrid.cs => DistanceSnapGrid.cs} (92%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs similarity index 94% rename from osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index 7399f12372..da7708081b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -20,7 +20,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Tests { - public class TestSceneOsuBeatSnapGrid : ManualInputManagerTestScene + public class TestSceneOsuDistanceSnapGrid : ManualInputManagerTestScene { private const double beat_length = 100; private static readonly Vector2 grid_position = new Vector2(512, 384); @@ -31,9 +31,9 @@ namespace osu.Game.Rulesets.Osu.Tests [Cached] private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - private TestOsuBeatSnapGrid grid; + private TestOsuDistanceSnapGrid grid; - public TestSceneOsuBeatSnapGrid() + public TestSceneOsuDistanceSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); @@ -152,7 +152,7 @@ namespace osu.Game.Rulesets.Osu.Tests RelativeSizeAxes = Axes.Both, Colour = Color4.SlateGray }, - grid = new TestOsuBeatSnapGrid(new HitCircle { Position = grid_position }), + grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }), new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnapPosition(grid.ToLocalSpace(v)) } }; }); @@ -197,11 +197,11 @@ namespace osu.Game.Rulesets.Osu.Tests } } - private class TestOsuBeatSnapGrid : OsuBeatSnapGrid + private class TestOsuDistanceSnapGrid : OsuDistanceSnapGrid { public new float DistanceSpacing => base.DistanceSpacing; - public TestOsuBeatSnapGrid(OsuHitObject hitObject) + public TestOsuDistanceSnapGrid(OsuHitObject hitObject) : base(hitObject) { } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs similarity index 90% rename from osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs rename to osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index d453e3d062..558993f8b2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -8,14 +8,14 @@ using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Osu.Edit { - public class OsuBeatSnapGrid : CircularBeatSnapGrid + public class OsuDistanceSnapGrid : CircularDistanceSnapGrid { /// /// Scoring distance with a speed-adjusted beat length of 1 second. /// private const float base_scoring_distance = 100; - public OsuBeatSnapGrid(OsuHitObject hitObject) + public OsuDistanceSnapGrid(OsuHitObject hitObject) : base(hitObject, hitObject.StackedEndPosition) { } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs similarity index 93% rename from osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs rename to osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index 073cec7315..a9e5930478 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneBeatSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Editor { - public class TestSceneBeatSnapGrid : EditorClockTestScene + public class TestSceneDistanceSnapGrid : EditorClockTestScene { private const double beat_length = 100; private static readonly Vector2 grid_position = new Vector2(512, 384); @@ -27,9 +27,9 @@ namespace osu.Game.Tests.Visual.Editor [Cached(typeof(IEditorBeatmap))] private readonly EditorBeatmap editorBeatmap; - private TestBeatSnapGrid grid; + private TestDistanceSnapGrid grid; - public TestSceneBeatSnapGrid() + public TestSceneDistanceSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); @@ -112,7 +112,7 @@ namespace osu.Game.Tests.Visual.Editor AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnapTime(snapPosition), 0.01)); } - private void createGrid(Action func = null, string description = null) + private void createGrid(Action func = null, string description = null) { AddStep($"create grid {description ?? string.Empty}", () => { @@ -123,20 +123,20 @@ namespace osu.Game.Tests.Visual.Editor RelativeSizeAxes = Axes.Both, Colour = Color4.SlateGray }, - grid = new TestBeatSnapGrid(new HitObject(), grid_position) + grid = new TestDistanceSnapGrid(new HitObject(), grid_position) }; func?.Invoke(grid); }); } - private class TestBeatSnapGrid : BeatSnapGrid + private class TestDistanceSnapGrid : DistanceSnapGrid { public new float Velocity = 1; public new float DistanceSpacing => base.DistanceSpacing; - public TestBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) + public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) : base(hitObject, centrePosition) { } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs similarity index 92% rename from osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs rename to osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index 5e378f8393..3cbf926d4f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularBeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -9,9 +9,9 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { - public abstract class CircularBeatSnapGrid : BeatSnapGrid + public abstract class CircularDistanceSnapGrid : DistanceSnapGrid { - protected CircularBeatSnapGrid(HitObject hitObject, Vector2 centrePosition) + protected CircularDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) : base(hitObject, centrePosition) { } diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs similarity index 92% rename from osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs rename to osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 9040843144..299e78b7c0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -15,7 +15,10 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { - public abstract class BeatSnapGrid : CompositeDrawable + /// + /// A grid which takes user input and returns a quantized ("snapped") position and time. + /// + public abstract class DistanceSnapGrid : CompositeDrawable { /// /// The velocity of the beatmap at the point of placement in pixels per millisecond. @@ -48,7 +51,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private double startTime; private double beatLength; - protected BeatSnapGrid(HitObject hitObject, Vector2 centrePosition) + protected DistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) { this.hitObject = hitObject; this.CentrePosition = centrePosition; @@ -114,14 +117,14 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Snaps a position to this grid. /// - /// The original position in coordinate space local to this . - /// The snapped position in coordinate space local to this . + /// The original position in coordinate space local to this . + /// The snapped position in coordinate space local to this . public abstract Vector2 GetSnapPosition(Vector2 position); /// /// Retrieves the time at a snapped position. /// - /// The snapped position in coordinate space local to this . + /// The snapped position in coordinate space local to this . /// The time at the snapped position. public double GetSnapTime(Vector2 position) => startTime + (position - CentrePosition).Length / Velocity; From f3ed71d3361bd1eb81e23432e457f93c54caa3db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 17 Oct 2019 16:36:47 +0900 Subject: [PATCH 8/8] Move scoring distance constant to a central/shared location --- osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs | 7 +------ osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 8 ++++++++ osu.Game.Rulesets.Osu/Objects/Slider.cs | 7 +------ 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index 558993f8b2..f701712739 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -10,11 +10,6 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuDistanceSnapGrid : CircularDistanceSnapGrid { - /// - /// Scoring distance with a speed-adjusted beat length of 1 second. - /// - private const float base_scoring_distance = 100; - public OsuDistanceSnapGrid(OsuHitObject hitObject) : base(hitObject, hitObject.StackedEndPosition) { @@ -25,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Edit TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time); - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + double scoringDistance = OsuHitObject.BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; return (float)(scoringDistance / timingPoint.BeatLength); } diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 80e013fe68..b506c1f918 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -14,8 +14,16 @@ namespace osu.Game.Rulesets.Osu.Objects { public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition { + /// + /// The radius of hit objects (ie. the radius of a ). + /// public const float OBJECT_RADIUS = 64; + /// + /// Scoring distance with a speed-adjusted beat length of 1 second (ie. the speed slider balls move through their track). + /// + internal const float BASE_SCORING_DISTANCE = 100; + public double TimePreempt = 600; public double TimeFadeIn = 400; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 9bed123465..d98d72331a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -19,11 +19,6 @@ namespace osu.Game.Rulesets.Osu.Objects { public class Slider : OsuHitObject, IHasCurve { - /// - /// Scoring distance with a speed-adjusted beat length of 1 second. - /// - private const float base_scoring_distance = 100; - public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public double Duration => EndTime - StartTime; @@ -123,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Objects TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); - double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; + double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; Velocity = scoringDistance / timingPoint.BeatLength; TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;