1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 11:20:04 +08:00

Merge pull request #26512 from OliBomby/slider-pathtype-update

Fix glitchy path type correction for sliders in the editor
This commit is contained in:
Dean Herbert 2024-01-15 16:19:24 +09:00 committed by GitHub
commit 6c0e968727
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 60 additions and 48 deletions

View File

@ -4,20 +4,15 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Framework.Utils;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@ -41,8 +36,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
public Action<DragEvent> DragInProgress;
public Action DragEnded;
public List<PathControlPoint> PointsInSegment;
public readonly BindableBool IsSelected = new BindableBool();
public readonly PathControlPoint ControlPoint;
@ -56,27 +49,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private IBindable<Vector2> hitObjectPosition;
private IBindable<float> hitObjectScale;
[UsedImplicitly]
private readonly IBindable<int> hitObjectVersion;
public PathControlPointPiece(T hitObject, PathControlPoint controlPoint)
{
this.hitObject = hitObject;
ControlPoint = controlPoint;
// we don't want to run the path type update on construction as it may inadvertently change the hit object.
cachePoints(hitObject);
hitObjectVersion = hitObject.Path.Version.GetBoundCopy();
// schedule ensure that updates are only applied after all operations from a single frame are applied.
// this avoids inadvertently changing the hit object path type for batch operations.
hitObjectVersion.BindValueChanged(_ => Scheduler.AddOnce(() =>
{
cachePoints(hitObject);
updatePathType();
}));
controlPoint.Changed += updateMarkerDisplay;
Origin = Anchor.Centre;
@ -214,28 +191,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
protected override void OnDragEnd(DragEndEvent e) => DragEnded?.Invoke();
private void cachePoints(T hitObject) => PointsInSegment = hitObject.Path.PointsInSegment(ControlPoint);
/// <summary>
/// Handles correction of invalid path types.
/// </summary>
private void updatePathType()
{
if (ControlPoint.Type != PathType.PERFECT_CURVE)
return;
if (PointsInSegment.Count > 3)
ControlPoint.Type = PathType.BEZIER;
if (PointsInSegment.Count != 3)
return;
ReadOnlySpan<Vector2> points = PointsInSegment.Select(p => p.Position).ToArray();
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points);
if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
ControlPoint.Type = PathType.BEZIER;
}
/// <summary>
/// Updates the state of the circular control point marker.
/// </summary>

View File

@ -14,10 +14,12 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
@ -76,6 +78,50 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
controlPoints.BindTo(hitObject.Path.ControlPoints);
}
/// <summary>
/// Handles correction of invalid path types.
/// </summary>
public void EnsureValidPathTypes()
{
List<PathControlPoint> pointsInCurrentSegment = new List<PathControlPoint>();
foreach (var controlPoint in controlPoints)
{
if (controlPoint.Type != null)
{
pointsInCurrentSegment.Add(controlPoint);
ensureValidPathType(pointsInCurrentSegment);
pointsInCurrentSegment.Clear();
}
pointsInCurrentSegment.Add(controlPoint);
}
ensureValidPathType(pointsInCurrentSegment);
}
private void ensureValidPathType(IReadOnlyList<PathControlPoint> segment)
{
if (segment.Count == 0)
return;
var first = segment[0];
if (first.Type != PathType.PERFECT_CURVE)
return;
if (segment.Count > 3)
first.Type = PathType.BEZIER;
if (segment.Count != 3)
return;
ReadOnlySpan<Vector2> points = segment.Select(p => p.Position).ToArray();
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points);
if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
first.Type = PathType.BEZIER;
}
/// <summary>
/// Selects the <see cref="PathControlPointPiece{T}"/> corresponding to the given <paramref name="pathControlPoint"/>,
/// and deselects all other <see cref="PathControlPointPiece{T}"/>s.
@ -240,7 +286,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
/// <param name="type">The path type we want to assign to the given control point piece.</param>
private void updatePathType(PathControlPointPiece<T> piece, PathType? type)
{
int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint);
var pointsInSegment = hitObject.Path.PointsInSegment(piece.ControlPoint);
int indexInSegment = pointsInSegment.IndexOf(piece.ControlPoint);
if (type?.Type == SplineType.PerfectCurve)
{
@ -249,8 +296,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
// and one segment of the previous type.
int thirdPointIndex = indexInSegment + 2;
if (piece.PointsInSegment.Count > thirdPointIndex + 1)
piece.PointsInSegment[thirdPointIndex].Type = piece.PointsInSegment[0].Type;
if (pointsInSegment.Count > thirdPointIndex + 1)
pointsInSegment[thirdPointIndex].Type = pointsInSegment[0].Type;
}
hitObject.Path.ExpectedDistance.Value = null;
@ -339,6 +386,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
// Maintain the path types in case they got defaulted to bezier at some point during the drag.
for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
hitObject.Path.ControlPoints[i].Type = dragPathTypes[i];
EnsureValidPathTypes();
}
public void DragEnded() => changeHandler?.EndChange();
@ -412,6 +461,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
foreach (var p in Pieces.Where(p => p.IsSelected.Value))
updatePathType(p, type);
EnsureValidPathTypes();
});
if (countOfState == totalCount)

View File

@ -267,6 +267,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
segmentStart.Type = PathType.BEZIER;
break;
}
controlPointVisualiser.EnsureValidPathTypes();
}
private void updateCursor()

View File

@ -254,6 +254,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
// Move the control points from the insertion index onwards to make room for the insertion
controlPoints.Insert(insertionIndex, pathControlPoint);
ControlPointVisualiser?.EnsureValidPathTypes();
HitObject.SnapTo(distanceSnapProvider);
return pathControlPoint;
@ -275,6 +277,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
controlPoints.Remove(c);
}
ControlPointVisualiser?.EnsureValidPathTypes();
// Snap the slider to the current beat divisor before checking length validity.
HitObject.SnapTo(distanceSnapProvider);