diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 2a6d6ce4c3..468d8ae9f5 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Utils; using osuTK; using osuTK.Input; @@ -42,7 +43,7 @@ protected override void OnSelectionChanged() { base.OnSelectionChanged(); - Quad quad = selectedMovableObjects.Length > 0 ? getSurroundingQuad(selectedMovableObjects) : new Quad(); + Quad quad = selectedMovableObjects.Length > 0 ? GeometryUtils.GetSurroundingQuad(selectedMovableObjects) : new Quad(); SelectionBox.CanRotate = quad.Width > 0 || quad.Height > 0; SelectionBox.CanFlipX = SelectionBox.CanScaleX = quad.Width > 0; @@ -109,13 +110,13 @@ public override bool HandleFlip(Direction direction, bool flipOverOrigin) { var hitObjects = selectedMovableObjects; - var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : getSurroundingQuad(hitObjects); + var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : GeometryUtils.GetSurroundingQuad(hitObjects); bool didFlip = false; foreach (var h in hitObjects) { - var flippedPosition = GetFlippedPosition(direction, flipQuad, h.Position); + var flippedPosition = GeometryUtils.GetFlippedPosition(direction, flipQuad, h.Position); if (!Precision.AlmostEquals(flippedPosition, h.Position)) { @@ -173,18 +174,18 @@ public override bool HandleRotation(float delta) { var hitObjects = selectedMovableObjects; - Quad quad = getSurroundingQuad(hitObjects); + Quad quad = GeometryUtils.GetSurroundingQuad(hitObjects); referenceOrigin ??= quad.Centre; foreach (var h in hitObjects) { - h.Position = RotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta); + h.Position = GeometryUtils.RotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta); if (h is IHasPath path) { foreach (PathControlPoint cp in path.Path.ControlPoints) - cp.Position = RotatePointAroundOrigin(cp.Position, Vector2.Zero, delta); + cp.Position = GeometryUtils.RotatePointAroundOrigin(cp.Position, Vector2.Zero, delta); } } @@ -196,7 +197,7 @@ private void scaleSlider(Slider slider, Vector2 scale) { referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type).ToList(); - Quad sliderQuad = GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position)); + Quad sliderQuad = GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position)); // Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0. scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size; @@ -222,7 +223,7 @@ private void scaleSlider(Slider slider, Vector2 scale) slider.SnapTo(snapProvider); //if sliderhead or sliderend end up outside playfield, revert scaling. - Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider }); + Quad scaledQuad = GeometryUtils.GetSurroundingQuad(new OsuHitObject[] { slider }); (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); if (xInBounds && yInBounds && slider.Path.HasValidLength) @@ -238,10 +239,10 @@ private void scaleSlider(Slider slider, Vector2 scale) private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) { scale = getClampedScale(hitObjects, reference, scale); - Quad selectionQuad = getSurroundingQuad(hitObjects); + Quad selectionQuad = GeometryUtils.GetSurroundingQuad(hitObjects); foreach (var h in hitObjects) - h.Position = GetScaledPosition(reference, scale, selectionQuad, h.Position); + h.Position = GeometryUtils.GetScaledPosition(reference, scale, selectionQuad, h.Position); } private (bool X, bool Y) isQuadInBounds(Quad quad) @@ -256,7 +257,7 @@ private void moveSelectionInBounds() { var hitObjects = selectedMovableObjects; - Quad quad = getSurroundingQuad(hitObjects); + Quad quad = GeometryUtils.GetSurroundingQuad(hitObjects); Vector2 delta = Vector2.Zero; @@ -286,7 +287,7 @@ private Vector2 getClampedScale(OsuHitObject[] hitObjects, Anchor reference, Vec float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0; float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0; - Quad selectionQuad = getSurroundingQuad(hitObjects); + Quad selectionQuad = GeometryUtils.GetSurroundingQuad(hitObjects); //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. Quad scaledQuad = new Quad(selectionQuad.TopLeft.X + xOffset, selectionQuad.TopLeft.Y + yOffset, selectionQuad.Width + scale.X, selectionQuad.Height + scale.Y); @@ -311,26 +312,6 @@ private Vector2 getClampedScale(OsuHitObject[] hitObjects, Anchor reference, Vec return scale; } - /// - /// Returns a gamefield-space quad surrounding the provided hit objects. - /// - /// The hit objects to calculate a quad for. - private Quad getSurroundingQuad(OsuHitObject[] hitObjects) => - GetSurroundingQuad(hitObjects.SelectMany(h => - { - if (h is IHasPath path) - { - return new[] - { - h.Position, - // can't use EndPosition for reverse slider cases. - h.Position + path.Path.PositionAt(1) - }; - } - - return new[] { h.Position }; - })); - /// /// All osu! hitobjects which can be moved/rotated/scaled. /// diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index b43f4eeb00..4a1ddd9d69 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -16,6 +16,7 @@ using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Skinning; +using osu.Game.Utils; using osuTK; namespace osu.Game.Overlays.SkinEditor @@ -40,7 +41,7 @@ public override bool HandleRotation(float angle) { var drawableItem = (Drawable)b.Item; - var rotatedPosition = RotatePointAroundOrigin(b.ScreenSpaceSelectionPoint, selectionQuad.Centre, angle); + var rotatedPosition = GeometryUtils.RotatePointAroundOrigin(b.ScreenSpaceSelectionPoint, selectionQuad.Centre, angle); updateDrawablePosition(drawableItem, rotatedPosition); drawableItem.Rotation += angle; @@ -137,7 +138,7 @@ public override bool HandleFlip(Direction direction, bool flipOverOrigin) { var drawableItem = (Drawable)b.Item; - var flippedPosition = GetFlippedPosition(direction, flipOverOrigin ? drawableItem.Parent.ScreenSpaceDrawQuad : selectionQuad, b.ScreenSpaceSelectionPoint); + var flippedPosition = GeometryUtils.GetFlippedPosition(direction, flipOverOrigin ? drawableItem.Parent.ScreenSpaceDrawQuad : selectionQuad, b.ScreenSpaceSelectionPoint); updateDrawablePosition(drawableItem, flippedPosition); @@ -275,7 +276,7 @@ private void applyOrigins(Anchor origin) /// /// private Quad getSelectionQuad() => - GetSurroundingQuad(SelectedBlueprints.SelectMany(b => b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); + GeometryUtils.GetSurroundingQuad(SelectedBlueprints.SelectMany(b => b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); private void applyFixedAnchors(Anchor anchor) { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 052cb18a5d..9b44b15fe4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -16,7 +16,6 @@ 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.Input.Bindings; using osu.Game.Resources.Localisation.Web; @@ -401,98 +400,5 @@ protected virtual IEnumerable GetContextMenuItemsForSelection(IEnumera => Enumerable.Empty(); #endregion - - #region Helper Methods - - /// - /// Rotate a point around an arbitrary origin. - /// - /// The point. - /// The centre origin to rotate around. - /// The angle to rotate (in degrees). - protected static Vector2 RotatePointAroundOrigin(Vector2 point, Vector2 origin, float angle) - { - angle = -angle; - - point.X -= origin.X; - point.Y -= origin.Y; - - Vector2 ret; - ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(MathUtils.DegreesToRadians(angle)); - ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(MathUtils.DegreesToRadians(angle)); - - ret.X += origin.X; - ret.Y += origin.Y; - - return ret; - } - - /// - /// Given a flip direction, a surrounding quad for all selected objects, and a position, - /// will return the flipped position in screen space coordinates. - /// - protected static Vector2 GetFlippedPosition(Direction direction, Quad quad, Vector2 position) - { - var centre = quad.Centre; - - switch (direction) - { - case Direction.Horizontal: - position.X = centre.X - (position.X - centre.X); - break; - - case Direction.Vertical: - position.Y = centre.Y - (position.Y - centre.Y); - break; - } - - return position; - } - - /// - /// Given a scale vector, a surrounding quad for all selected objects, and a position, - /// will return the scaled position in screen space coordinates. - /// - protected static Vector2 GetScaledPosition(Anchor reference, Vector2 scale, Quad selectionQuad, Vector2 position) - { - // adjust the direction of scale depending on which side the user is dragging. - float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0; - float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0; - - // guard against no-ops and NaN. - if (scale.X != 0 && selectionQuad.Width > 0) - position.X = selectionQuad.TopLeft.X + xOffset + (position.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X); - - if (scale.Y != 0 && selectionQuad.Height > 0) - position.Y = selectionQuad.TopLeft.Y + yOffset + (position.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y); - - return position; - } - - /// - /// Returns a quad surrounding the provided points. - /// - /// The points to calculate a quad for. - protected static Quad GetSurroundingQuad(IEnumerable points) - { - if (!points.Any()) - return new Quad(); - - Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); - Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); - - // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted - foreach (var p in points) - { - minPosition = Vector2.ComponentMin(minPosition, p); - maxPosition = Vector2.ComponentMax(maxPosition, p); - } - - Vector2 size = maxPosition - minPosition; - - return new Quad(minPosition.X, minPosition.Y, size.X, size.Y); - } - - #endregion } } diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs new file mode 100644 index 0000000000..725e93d098 --- /dev/null +++ b/osu.Game/Utils/GeometryUtils.cs @@ -0,0 +1,126 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Utils; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Utils +{ + public static class GeometryUtils + { + /// + /// Rotate a point around an arbitrary origin. + /// + /// The point. + /// The centre origin to rotate around. + /// The angle to rotate (in degrees). + public static Vector2 RotatePointAroundOrigin(Vector2 point, Vector2 origin, float angle) + { + angle = -angle; + + point.X -= origin.X; + point.Y -= origin.Y; + + Vector2 ret; + ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(MathUtils.DegreesToRadians(angle)); + ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(MathUtils.DegreesToRadians(angle)); + + ret.X += origin.X; + ret.Y += origin.Y; + + return ret; + } + + /// + /// Given a flip direction, a surrounding quad for all selected objects, and a position, + /// will return the flipped position in screen space coordinates. + /// + public static Vector2 GetFlippedPosition(Direction direction, Quad quad, Vector2 position) + { + var centre = quad.Centre; + + switch (direction) + { + case Direction.Horizontal: + position.X = centre.X - (position.X - centre.X); + break; + + case Direction.Vertical: + position.Y = centre.Y - (position.Y - centre.Y); + break; + } + + return position; + } + + /// + /// Given a scale vector, a surrounding quad for all selected objects, and a position, + /// will return the scaled position in screen space coordinates. + /// + public static Vector2 GetScaledPosition(Anchor reference, Vector2 scale, Quad selectionQuad, Vector2 position) + { + // adjust the direction of scale depending on which side the user is dragging. + float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0; + float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0; + + // guard against no-ops and NaN. + if (scale.X != 0 && selectionQuad.Width > 0) + position.X = selectionQuad.TopLeft.X + xOffset + (position.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X); + + if (scale.Y != 0 && selectionQuad.Height > 0) + position.Y = selectionQuad.TopLeft.Y + yOffset + (position.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y); + + return position; + } + + /// + /// Returns a quad surrounding the provided points. + /// + /// The points to calculate a quad for. + public static Quad GetSurroundingQuad(IEnumerable points) + { + if (!points.Any()) + return new Quad(); + + Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); + Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); + + // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted + foreach (var p in points) + { + minPosition = Vector2.ComponentMin(minPosition, p); + maxPosition = Vector2.ComponentMax(maxPosition, p); + } + + Vector2 size = maxPosition - minPosition; + + return new Quad(minPosition.X, minPosition.Y, size.X, size.Y); + } + + /// + /// Returns a gamefield-space quad surrounding the provided hit objects. + /// + /// The hit objects to calculate a quad for. + public static Quad GetSurroundingQuad(IEnumerable hitObjects) => + GetSurroundingQuad(hitObjects.SelectMany(h => + { + if (h is IHasPath path) + { + return new[] + { + h.Position, + // can't use EndPosition for reverse slider cases. + h.Position + path.Path.PositionAt(1) + }; + } + + return new[] { h.Position }; + })); + } +}