1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 16:07:24 +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));
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);
defaultOrigin = OriginalSurroundingQuad.Value.Centre;
}
@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Edit
}
else
{
scale = getClampedScale(OriginalSurroundingQuad.Value, actualOrigin, scale);
scale = GetClampedScale(scale, actualOrigin);
foreach (var (ho, originalState) in objectsInScale)
{
@ -155,30 +155,33 @@ namespace osu.Game.Rulesets.Osu.Edit
return (xInBounds, yInBounds);
}
/// <summary>
/// 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)
public override Vector2 GetClampedScale(Vector2 scale, Vector2? origin = null)
{
//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);
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);
Debug.Assert(defaultOrigin != null && OriginalSurroundingQuad != null);
if (!Precision.AlmostEquals(selectionQuad.TopLeft.X - origin.X, 0))
scale.X = selectionQuad.TopLeft.X - origin.X < 0 ? MathHelper.Clamp(scale.X, tl2.X, tl1.X) : MathHelper.Clamp(scale.X, tl1.X, tl2.X);
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);
if (!Precision.AlmostEquals(selectionQuad.BottomRight.X - origin.X, 0))
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))
scale.Y = selectionQuad.BottomRight.Y - origin.Y < 0 ? MathHelper.Clamp(scale.Y, br2.Y, br1.Y) : MathHelper.Clamp(scale.Y, br1.Y, br2.Y);
if (objectsInScale.Count == 1 && objectsInScale.First().Key is Slider slider)
origin = slider.Position;
Vector2 actualOrigin = origin ?? defaultOrigin.Value;
var selectionQuad = OriginalSurroundingQuad.Value;
var tl1 = Vector2.Divide(-actualOrigin, selectionQuad.TopLeft - actualOrigin);
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));
}

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 System.Linq;
using osu.Framework.Allocation;
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 SliderWithTextBoxInput<float> scaleInput = null!;
private BindableNumber<float> scaleInputBindable = null!;
private EditorRadioButtonCollection scaleOrigin = null!;
private RadioButton selectionCentreButton = null!;
@ -45,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{
scaleInput = new SliderWithTextBoxInput<float>("Scale:")
{
Current = new BindableNumber<float>
Current = scaleInputBindable = new BindableNumber<float>
{
MinValue = 0.5f,
MaxValue = 2,
@ -61,10 +63,10 @@ namespace osu.Game.Rulesets.Osu.Edit
Items = new[]
{
new RadioButton("Playfield centre",
() => scaleInfo.Value = scaleInfo.Value with { Origin = ScaleOrigin.PlayfieldCentre },
() => setOrigin(ScaleOrigin.PlayfieldCentre),
() => new SpriteIcon { Icon = FontAwesome.Regular.Square }),
selectionCentreButton = new RadioButton("Selection centre",
() => scaleInfo.Value = scaleInfo.Value with { Origin = ScaleOrigin.SelectionCentre },
() => setOrigin(ScaleOrigin.SelectionCentre),
() => new SpriteIcon { Icon = FontAwesome.Solid.VectorSquare })
}
}
@ -96,14 +98,39 @@ namespace osu.Game.Rulesets.Osu.Edit
scaleInfo.BindValueChanged(scale =>
{
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()
{
base.PopIn();
scaleHandler.Begin();
updateMaxScale();
}
protected override void PopOut()

View File

@ -34,6 +34,14 @@ namespace osu.Game.Screens.Edit.Compose.Components
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>
/// Performs a single, instant, atomic scale operation.
/// </summary>