Skip to content

Commit

Permalink
Merge pull request #30080 from OliBomby/clamp-scale2
Browse files Browse the repository at this point in the history
Clamp scale with lower and upper bounds
  • Loading branch information
bdach authored Oct 14, 2024
2 parents f8bce70 + 5db1f05 commit 74675c8
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 16 deletions.
59 changes: 47 additions & 12 deletions osu.Game.Rulesets.Osu/Edit/OsuSelectionScaleHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -240,39 +240,74 @@ public Vector2 ClampScaleToPlayfieldBounds(Vector2 scale, Vector2? origin = null
points = originalConvexHull!;

foreach (var point in points)
{
scale = clampToBound(scale, point, Vector2.Zero);
scale = clampToBound(scale, point, OsuPlayfield.BASE_SIZE);
}
scale = clampToBounds(scale, point, Vector2.Zero, OsuPlayfield.BASE_SIZE);

return Vector2.ComponentMax(scale, new Vector2(Precision.FLOAT_EPSILON));

float minPositiveComponent(Vector2 v) => MathF.Min(v.X < 0 ? float.PositiveInfinity : v.X, v.Y < 0 ? float.PositiveInfinity : v.Y);
return scale;

Vector2 clampToBound(Vector2 s, Vector2 p, Vector2 bound)
// Clamps the scale vector s such that the point p scaled by s is within the rectangle defined by lowerBounds and upperBounds
Vector2 clampToBounds(Vector2 s, Vector2 p, Vector2 lowerBounds, Vector2 upperBounds)
{
p -= actualOrigin;
bound -= actualOrigin;
lowerBounds -= actualOrigin;
upperBounds -= actualOrigin;
// a.X is the rotated X component of p with respect to the X bounds
// a.Y is the rotated X component of p with respect to the Y bounds
// b.X is the rotated Y component of p with respect to the X bounds
// b.Y is the rotated Y component of p with respect to the Y bounds
var a = new Vector2(cos * cos * p.X - sin * cos * p.Y, -sin * cos * p.X + sin * sin * p.Y);
var b = new Vector2(sin * sin * p.X + sin * cos * p.Y, sin * cos * p.X + cos * cos * p.Y);

float sLowerBound, sUpperBound;

switch (adjustAxis)
{
case Axes.X:
s.X = MathF.Min(scale.X, minPositiveComponent(Vector2.Divide(bound - b, a)));
(sLowerBound, sUpperBound) = computeBounds(lowerBounds - b, upperBounds - b, a);
s.X = MathHelper.Clamp(s.X, sLowerBound, sUpperBound);
break;

case Axes.Y:
s.Y = MathF.Min(scale.Y, minPositiveComponent(Vector2.Divide(bound - a, b)));
(sLowerBound, sUpperBound) = computeBounds(lowerBounds - a, upperBounds - a, b);
s.Y = MathHelper.Clamp(s.Y, sLowerBound, sUpperBound);
break;

case Axes.Both:
s = Vector2.ComponentMin(s, s * minPositiveComponent(Vector2.Divide(bound, a * s.X + b * s.Y)));
// Here we compute the bounds for the magnitude multiplier of the scale vector
// Therefore the ratio s.X / s.Y will be maintained
(sLowerBound, sUpperBound) = computeBounds(lowerBounds, upperBounds, a * s.X + b * s.Y);
s.X = s.X < 0
? MathHelper.Clamp(s.X, s.X * sUpperBound, s.X * sLowerBound)
: MathHelper.Clamp(s.X, s.X * sLowerBound, s.X * sUpperBound);
s.Y = s.Y < 0
? MathHelper.Clamp(s.Y, s.Y * sUpperBound, s.Y * sLowerBound)
: MathHelper.Clamp(s.Y, s.Y * sLowerBound, s.Y * sUpperBound);
break;
}

return s;
}

// Computes the bounds for the magnitude of the scaled point p with respect to the bounds lowerBounds and upperBounds
(float, float) computeBounds(Vector2 lowerBounds, Vector2 upperBounds, Vector2 p)
{
var sLowerBounds = Vector2.Divide(lowerBounds, p);
var sUpperBounds = Vector2.Divide(upperBounds, p);

// If the point is negative, then the bounds are flipped
if (p.X < 0)
(sLowerBounds.X, sUpperBounds.X) = (sUpperBounds.X, sLowerBounds.X);
if (p.Y < 0)
(sLowerBounds.Y, sUpperBounds.Y) = (sUpperBounds.Y, sLowerBounds.Y);

// If the point is at zero, then any scale will have no effect on the point so the bounds are infinite
// The float division would already give us infinity for the bounds, but the sign is not consistent so we have to manually set it
if (Precision.AlmostEquals(p.X, 0))
(sLowerBounds.X, sUpperBounds.X) = (float.NegativeInfinity, float.PositiveInfinity);
if (Precision.AlmostEquals(p.Y, 0))
(sLowerBounds.Y, sUpperBounds.Y) = (float.NegativeInfinity, float.PositiveInfinity);

return (MathF.Max(sLowerBounds.X, sLowerBounds.Y), MathF.Min(sUpperBounds.X, sUpperBounds.Y));
}
}

private void moveSelectionInBounds()
Expand Down
19 changes: 15 additions & 4 deletions osu.Game.Rulesets.Osu/Edit/PreciseScalePopover.cs
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,14 @@ private void toggleAxisAvailable(Bindable<bool> axisBindable, bool available)
axisBindable.Disabled = !available;
}

private void updateMaxScale()
private void updateMinMaxScale()
{
if (!scaleHandler.OriginalSurroundingQuad.HasValue)
return;

const float min_scale = 0.5f;
const float max_scale = 10;

var scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(max_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value));

if (!scaleInfo.Value.XAxis)
Expand All @@ -189,12 +191,21 @@ private void updateMaxScale()
scale.Y = max_scale;

scaleInputBindable.MaxValue = MathF.Max(1, MathF.Min(scale.X, scale.Y));

scale = scaleHandler.ClampScaleToPlayfieldBounds(new Vector2(min_scale), getOriginPosition(scaleInfo.Value), getAdjustAxis(scaleInfo.Value), getRotation(scaleInfo.Value));

if (!scaleInfo.Value.XAxis)
scale.X = min_scale;
if (!scaleInfo.Value.YAxis)
scale.Y = min_scale;

scaleInputBindable.MinValue = MathF.Min(1, MathF.Max(scale.X, scale.Y));
}

private void setOrigin(ScaleOrigin origin)
{
scaleInfo.Value = scaleInfo.Value with { Origin = origin };
updateMaxScale();
updateMinMaxScale();
updateAxisCheckBoxesEnabled();
}

Expand Down Expand Up @@ -226,14 +237,14 @@ private void setOrigin(ScaleOrigin origin)
private void setAxis(bool x, bool y)
{
scaleInfo.Value = scaleInfo.Value with { XAxis = x, YAxis = y };
updateMaxScale();
updateMinMaxScale();
}

protected override void PopIn()
{
base.PopIn();
scaleHandler.Begin();
updateMaxScale();
updateMinMaxScale();
}

protected override void PopOut()
Expand Down

0 comments on commit 74675c8

Please sign in to comment.