1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 18:47:27 +08:00

calculate max scale bounds for scale slider

This commit is contained in:
OliBomby 2024-05-25 20:17:27 +02:00
parent 88314dc584
commit 4eeebdf60c
3 changed files with 64 additions and 26 deletions

View File

@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Edit
objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho)); objectsInScale = selectedMovableObjects.ToDictionary(ho => ho, ho => new OriginalHitObjectState(ho));
OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider OriginalSurroundingQuad = objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider
? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position)) ? GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => slider.Position + p.Position))
: GeometryUtils.GetSurroundingQuad(objectsInScale.Keys); : GeometryUtils.GetSurroundingQuad(objectsInScale.Keys);
defaultOrigin = OriginalSurroundingQuad.Value.Centre; defaultOrigin = OriginalSurroundingQuad.Value.Centre;
} }
@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Edit
} }
else else
{ {
scale = getClampedScale(OriginalSurroundingQuad.Value, actualOrigin, scale); scale = GetClampedScale(scale, actualOrigin);
foreach (var (ho, originalState) in objectsInScale) foreach (var (ho, originalState) in objectsInScale)
{ {
@ -155,30 +155,33 @@ namespace osu.Game.Rulesets.Osu.Edit
return (xInBounds, yInBounds); return (xInBounds, yInBounds);
} }
/// <summary> public override Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null)
/// Clamp scale for multi-object-scaling where selection does not exceed playfield bounds or flip.
/// </summary>
/// <param name="selectionQuad">The quad surrounding the hitobjects</param>
/// <param name="origin">The origin from which the scale operation is performed</param>
/// <param name="scale">The scale to be clamped</param>
/// <returns>The clamped scale vector</returns>
private Vector2 getClampedScale(Quad selectionQuad, Vector2 origin, Vector2 scale)
{ {
//todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead.
if (objectsInScale == null)
return scale;
var tl1 = Vector2.Divide(-origin, selectionQuad.TopLeft - origin); Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null);
var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.TopLeft - origin);
var br1 = Vector2.Divide(-origin, selectionQuad.BottomRight - origin);
var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - origin, selectionQuad.BottomRight - origin);
if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - origin.X, 0)) if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider)
scale.X = selectionQuad.TopLeft.X - origin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X); origin = slider.Position;
if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - origin.Y, 0))
scale.Y = selectionQuad.TopLeft.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y); Vector2 actualOrigin = origin ?? defaultOrigin.Value;
if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - origin.X, 0)) var selectionQuad = OriginalSurroundingQuad.Value;
scale.X = selectionQuad.BottomRight.X - origin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X);
if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - origin.Y, 0)) var tl1 = Vector2.Divide(-actualOrigin, selectionQuad.TopLeft - actualOrigin);
scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y); var tl2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.TopLeft - actualOrigin);
var br1 = Vector2.Divide(-actualOrigin, selectionQuad.BottomRight - actualOrigin);
var br2 = Vector2.Divide(OsuPlayfield.BASE_SIZE - actualOrigin, selectionQuad.BottomRight - actualOrigin);
if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - actualOrigin.X, 0))
scale.X = selectionQuad.TopLeft.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X);
if (!Precision.AlmostEquals(selectionQuad.TopLeft.Y - actualOrigin.Y, 0))
scale.Y = selectionQuad.TopLeft.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, tl2.Y, tl1.Y) : MathHelper.Clamp(scale.Y, tl1.Y, tl2.Y);
if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - actualOrigin.X, 0))
scale.X = selectionQuad.BottomRight.X - actualOrigin.X < 0 ? MathHelper.Clamp(scale.X, br2.X, br1.X) : MathHelper.Clamp(scale.X, br1.X, br2.X);
if (!Precision.AlmostEquals(selectionQuad.BottomRight.Y - actualOrigin.Y, 0))
scale.Y = selectionQuad.BottomRight.Y - actualOrigin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y);
return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON)); return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -22,6 +23,7 @@ namespace osu.Game.Rulesets.Osu.Edit
private readonly Bindable<PreciseScaleInfo> scaleInfo = new Bindable<PreciseScaleInfo>(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true)); private readonly Bindable<PreciseScaleInfo> scaleInfo = new Bindable<PreciseScaleInfo>(new PreciseScaleInfo(1, ScaleOrigin.PlayfieldCentre, true, true));
private SliderWithTextBoxInput<float> scaleInput = null!; private SliderWithTextBoxInput<float> scaleInput = null!;
private BindableNumber<float> scaleInputBindable = null!;
private EditorRadioButtonCollection scaleOrigin = null!; private EditorRadioButtonCollection scaleOrigin = null!;
private RadioButton selectionCentreButton = null!; private RadioButton selectionCentreButton = null!;
@ -45,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{ {
scaleInput = new SliderWithTextBoxInput<float>("Scale:") scaleInput = new SliderWithTextBoxInput<float>("Scale:")
{ {
Current = new BindableNumber<float> Current = scaleInputBindable = new BindableNumber<float>
{ {
MinValue = 0.5f, MinValue = 0.5f,
MaxValue = 2, MaxValue = 2,
@ -61,10 +63,10 @@ namespace osu.Game.Rulesets.Osu.Edit
Items = new[] Items = new[]
{ {
new RadioButton("Playfield centre", new RadioButton("Playfield centre",
() => scaleInfo.Value = scaleInfo.Value with { Origin = ScaleOrigin.PlayfieldCentre }, () => setOrigin(ScaleOrigin.PlayfieldCentre),
() => new SpriteIcon { Icon = FontAwesome.Regular.Square }), () => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
selectionCentreButton = new RadioButton("Selection centre", selectionCentreButton = new RadioButton("Selection centre",
() => scaleInfo.Value = scaleInfo.Value with { Origin = ScaleOrigin.SelectionCentre }, () => setOrigin(ScaleOrigin.SelectionCentre),
() => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare }) () => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare })
} }
} }
@ -96,14 +98,39 @@ namespace osu.Game.Rulesets.Osu.Edit
scaleInfo.BindValueChanged(scale => scaleInfo.BindValueChanged(scale =>
{ {
var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1); var newScale = new Vector2(scale.NewValue.XAxis ? scale.NewValue.Scale : 1, scale.NewValue.YAxis ? scale.NewValue.Scale : 1);
scaleHandler.Update(newScale, scale.NewValue.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null); scaleHandler.Update(newScale, getOriginPosition(scale.NewValue));
}); });
} }
private void updateMaxScale()
{
if (!scaleHandler.OriginalSurroundingQuad.HasValue)
return;
const float max_scale = 10;
var scale = scaleHandler.GetClampedScale(new Vector2(max_scale), getOriginPosition(scaleInfo.Value));
if (!scaleInfo.Value.XAxis)
scale.X = max_scale;
if (!scaleInfo.Value.YAxis)
scale.Y = max_scale;
scaleInputBindable.MaxValue = MathF.Max(1, MathF.Min(scale.X, scale.Y));
}
private void setOrigin(ScaleOrigin origin)
{
scaleInfo.Value = scaleInfo.Value with { Origin = origin };
updateMaxScale();
}
private Vector2? getOriginPosition(PreciseScaleInfo scale) => scale.Origin == ScaleOrigin.PlayfieldCentre ? OsuPlayfield.BASE_SIZE / 2 : null;
protected override void PopIn() protected override void PopIn()
{ {
base.PopIn(); base.PopIn();
scaleHandler.Begin(); scaleHandler.Begin();
updateMaxScale();
} }
protected override void PopOut() protected override void PopOut()

View File

@ -34,6 +34,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
public Quad? OriginalSurroundingQuad { get; protected set; } public Quad? OriginalSurroundingQuad { get; protected set; }
/// <summary>
/// Clamp scale where selection does not exceed playfield bounds or flip.
/// </summary>
/// <param name="origin">The origin from which the scale operation is performed</param>
/// <param name="scale">The scale to be clamped</param>
/// <returns>The clamped scale vector</returns>
public virtual Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null) => scale;
/// <summary> /// <summary>
/// Performs a single, instant, atomic scale operation. /// Performs a single, instant, atomic scale operation.
/// </summary> /// </summary>