Change snapping to be screen space coordinate based

This commit is contained in:
Dean Herbert 2020-05-20 17:48:43 +09:00
parent b6c9a50f7a
commit 3354d48a38
16 changed files with 65 additions and 41 deletions

View File

@ -77,6 +77,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
public override Quad SelectionQuad => ScreenSpaceDrawQuad;
public override Vector2 SelectionPoint => DrawableObject.Head.ScreenSpaceDrawQuad.Centre;
public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.Head.ScreenSpaceDrawQuad.Centre;
}
}

View File

@ -1,6 +1,7 @@
// 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 osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
@ -44,26 +45,31 @@ namespace osu.Game.Rulesets.Mania.Edit
public int TotalColumns => Playfield.TotalColumns;
public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time)
public override (Vector2 position, double time) SnapPositionToValidTime(Vector2 position)
{
throw new NotImplementedException();
}
public override (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
{
var hoc = Playfield.GetColumn(0).HitObjectContainer;
float targetPosition = hoc.ToLocalSpace(ToScreenSpace(position)).Y;
Vector2 targetPosition = hoc.ToLocalSpace(screenSpacePosition);
if (drawableRuleset.ScrollingInfo.Direction.Value == ScrollingDirection.Down)
{
// We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time.
// The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position,
// so when scrolling downwards the coordinates need to be flipped.
targetPosition = hoc.DrawHeight - targetPosition;
targetPosition.Y = hoc.DrawHeight - targetPosition.Y;
}
double targetTime = drawableRuleset.ScrollingInfo.Algorithm.TimeAt(targetPosition,
double targetTime = drawableRuleset.ScrollingInfo.Algorithm.TimeAt(targetPosition.Y,
EditorClock.CurrentTime,
drawableRuleset.ScrollingInfo.TimeRange.Value,
hoc.DrawHeight);
return base.GetSnappedPosition(position, targetTime);
return (targetPosition, targetTime);
}
protected override DrawableRuleset<ManiaHitObject> CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)

View File

@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[Cached]
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
[Cached(typeof(IDistanceSnapProvider))]
[Cached(typeof(IPositionSnapProvider))]
private readonly SnapProvider snapProvider = new SnapProvider();
private TestOsuDistanceSnapGrid grid;
@ -172,9 +172,11 @@ namespace osu.Game.Rulesets.Osu.Tests
}
}
private class SnapProvider : IDistanceSnapProvider
private class SnapProvider : IPositionSnapProvider
{
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time);
public (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) => (position, 0);
public (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => (screenSpacePosition, 0);
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private IEditorChangeHandler changeHandler { get; set; }
[Resolved(CanBeNull = true)]
private IDistanceSnapProvider snapProvider { get; set; }
private IPositionSnapProvider snapProvider { get; set; }
[Resolved]
private OsuColour colours { get; set; }
@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
if (ControlPoint == slider.Path.ControlPoints[0])
{
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
(Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime);
(Vector2 snappedPosition, double snappedTime) = snapProvider?.SnapScreenSpacePositionToValidTime(e.MousePosition) ?? (e.MousePosition, slider.StartTime);
Vector2 movementDelta = snappedPosition - slider.Position;
slider.Position += movementDelta;

View File

@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)),
};
public override Vector2 SelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre;
public override Vector2 ScreenSpaceSelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos);

View File

@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing
[Cached(typeof(EditorBeatmap))]
private readonly EditorBeatmap editorBeatmap;
[Cached(typeof(IDistanceSnapProvider))]
[Cached(typeof(IPositionSnapProvider))]
private readonly SnapProvider snapProvider = new SnapProvider();
public TestSceneDistanceSnapGrid()
@ -151,9 +151,11 @@ namespace osu.Game.Tests.Visual.Editing
=> (Vector2.Zero, 0);
}
private class SnapProvider : IDistanceSnapProvider
private class SnapProvider : IPositionSnapProvider
{
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time);
public (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) => (position, 0);
public (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => (screenSpacePosition, 0);
public float GetBeatSnapDistanceAt(double referenceTime) => 10;

View File

@ -245,8 +245,7 @@ namespace osu.Game.Rulesets.Edit
{
EditorBeatmap.PlacementObject.Value = hitObject;
if (distanceSnapGrid != null)
hitObject.StartTime = GetSnappedPosition(distanceSnapGrid.ToLocalSpace(inputManager.CurrentState.Mouse.Position), hitObject.StartTime).time;
hitObject.StartTime = SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position).time;
}
public void EndPlacement(HitObject hitObject, bool commit)
@ -265,7 +264,11 @@ namespace osu.Game.Rulesets.Edit
public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject);
public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => distanceSnapGrid?.GetSnappedPosition(position) ?? (position, time);
public override (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) =>
distanceSnapGrid?.GetSnappedPosition(position) ?? (position, 0);
public override (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
=> SnapPositionToValidTime(drawableRulesetWrapper.Playfield.ToLocalSpace(screenSpacePosition));
public override float GetBeatSnapDistanceAt(double referenceTime)
{
@ -297,8 +300,8 @@ namespace osu.Game.Rulesets.Edit
}
[Cached(typeof(HitObjectComposer))]
[Cached(typeof(IDistanceSnapProvider))]
public abstract class HitObjectComposer : CompositeDrawable, IDistanceSnapProvider
[Cached(typeof(IPositionSnapProvider))]
public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider
{
internal HitObjectComposer()
{
@ -323,7 +326,9 @@ namespace osu.Game.Rulesets.Edit
[CanBeNull]
protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable<HitObject> selectedHitObjects) => null;
public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time);
public abstract (Vector2 position, double time) SnapPositionToValidTime(Vector2 position);
public abstract (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition);
public abstract float GetBeatSnapDistanceAt(double referenceTime);

View File

@ -5,9 +5,16 @@ using osuTK;
namespace osu.Game.Rulesets.Edit
{
public interface IDistanceSnapProvider
public interface IPositionSnapProvider
{
(Vector2 position, double time) GetSnappedPosition(Vector2 position, double time);
/// <summary>
/// Given a position (local to the provider), find a valid time snap
/// </summary>
/// <param name="position">The local position to be snapped.</param>
/// <returns>The time and position post-snapping.</returns>
(Vector2 position, double time) SnapPositionToValidTime(Vector2 position);
(Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition);
/// <summary>
/// Retrieves the distance between two points within a timing point that are one beat length apart.

View File

@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Edit
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos);
public override Vector2 SelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre;
public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre;
public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad;

View File

@ -66,7 +66,10 @@ namespace osu.Game.Rulesets.Edit
protected void BeginPlacement(double? startTime = null, bool commitStart = false)
{
HitObject.StartTime = startTime ?? EditorClock.CurrentTime;
// applies snapping to above time
placementHandler.BeginPlacement(HitObject);
PlacementActive |= commitStart;
}

View File

@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Edit
/// <summary>
/// The screen-space point that causes this <see cref="OverlaySelectionBlueprint"/> to be selected.
/// </summary>
public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre;
public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre;
/// <summary>
/// The screen-space quad that outlines this <see cref="OverlaySelectionBlueprint"/> for selections.

View File

@ -49,7 +49,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
private readonly BindableList<HitObject> selectedHitObjects = new BindableList<HitObject>();
[Resolved(canBeNull: true)]
private IDistanceSnapProvider snapProvider { get; set; }
private IPositionSnapProvider snapProvider { get; set; }
protected BlueprintContainer()
{
@ -326,7 +326,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
{
foreach (var blueprint in SelectionBlueprints)
{
if (blueprint.IsAlive && blueprint.IsPresent && rect.Contains(blueprint.SelectionPoint))
if (blueprint.IsAlive && blueprint.IsPresent && rect.Contains(blueprint.ScreenSpaceSelectionPoint))
blueprint.Select();
else
blueprint.Deselect();
@ -384,7 +384,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
// Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject
movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).First();
movementBlueprintOriginalPosition = movementBlueprint.SelectionPoint; // todo: unsure if correct
movementBlueprintOriginalPosition = movementBlueprint.ScreenSpaceSelectionPoint; // todo: unsure if correct
}
/// <summary>
@ -405,7 +405,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
Vector2 movePosition = movementBlueprintOriginalPosition.Value + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition;
// Retrieve a snapped position.
(Vector2 snappedPosition, double snappedTime) = snapProvider.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime);
(Vector2 snappedPosition, double snappedTime) = snapProvider.SnapScreenSpacePositionToValidTime(movePosition);
// Move the hitobjects.
if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, ToScreenSpace(snappedPosition))))

View File

@ -67,10 +67,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
private void updatePlacementPosition(Vector2 screenSpacePosition)
{
Vector2 snappedGridPosition = composer.GetSnappedPosition(ToLocalSpace(screenSpacePosition), 0).position;
Vector2 snappedScreenSpacePosition = ToScreenSpace(snappedGridPosition);
Vector2 snappedPlayfieldPosition = composer.SnapScreenSpacePositionToValidTime(screenSpacePosition).position;
currentPlacement.UpdatePosition(snappedScreenSpacePosition);
currentPlacement.UpdatePosition(ToScreenSpace(snappedPlayfieldPosition));
}
#endregion

View File

@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected OsuColour Colours { get; private set; }
[Resolved]
protected IDistanceSnapProvider SnapProvider { get; private set; }
protected IPositionSnapProvider SnapProvider { get; private set; }
[Resolved]
private EditorBeatmap beatmap { get; set; }

View File

@ -17,9 +17,9 @@ using osuTK;
namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
[Cached(typeof(IDistanceSnapProvider))]
[Cached(typeof(IPositionSnapProvider))]
[Cached]
public class Timeline : ZoomableScrollContainer, IDistanceSnapProvider
public class Timeline : ZoomableScrollContainer, IPositionSnapProvider
{
public readonly Bindable<bool> WaveformVisible = new Bindable<bool>();
public readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
@ -181,12 +181,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
[Resolved]
private IBeatSnapProvider beatSnapProvider { get; set; }
public double GetTimeFromScreenSpacePosition(Vector2 position)
=> getTimeFromPosition(Content.ToLocalSpace(position));
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) =>
public (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) =>
(position, beatSnapProvider.SnapTime(getTimeFromPosition(position)));
public (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 position) =>
(position, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(position))));
private double getTimeFromPosition(Vector2 localPosition) =>
(localPosition.X / Content.DrawWidth) * track.Length;

View File

@ -186,7 +186,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
}
}
public override Vector2 SelectionPoint => ScreenSpaceDrawQuad.TopLeft;
public override Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.TopLeft;
public class DragBar : Container
{
@ -275,7 +275,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
OnDragHandled?.Invoke(e);
var time = timeline.GetTimeFromScreenSpacePosition(e.ScreenSpaceMousePosition);
var time = timeline.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition).time;
switch (hitObject)
{