Skip to content

Commit

Permalink
Merge pull request #28538 from bdach/polygon-tool
Browse files Browse the repository at this point in the history
Reimplement stable polygon tool
  • Loading branch information
peppy authored Aug 19, 2024
2 parents 95d0633 + 7248c04 commit 510f2c8
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 1 deletion.
54 changes: 54 additions & 0 deletions osu.Game.Rulesets.Osu/Edit/GenerateToolboxGroup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Screens.Edit.Components;
using osuTK;
using osuTK.Input;

namespace osu.Game.Rulesets.Osu.Edit
{
public partial class GenerateToolboxGroup : EditorToolboxGroup
{
private readonly EditorToolButton polygonButton;

public GenerateToolboxGroup()
: base("Generate")
{
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(5),
Children = new Drawable[]
{
polygonButton = new EditorToolButton("Polygon",
() => new SpriteIcon { Icon = FontAwesome.Solid.Spinner },
() => new PolygonGenerationPopover()),
}
};
}

protected override bool OnKeyDown(KeyDownEvent e)
{
if (e.Repeat) return false;

switch (e.Key)
{
case Key.D:
if (!e.ControlPressed || !e.ShiftPressed)
return false;

polygonButton.TriggerClick();
return true;

default:
return false;
}
}
}
}
3 changes: 2 additions & 1 deletion osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ protected override IEnumerable<TernaryButton> CreateTernaryButtons()
private Bindable<HitObject> placementObject;

[Cached(typeof(IDistanceSnapProvider))]
protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
public readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();

[Cached]
protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup();
Expand Down Expand Up @@ -110,6 +110,7 @@ private void load()
RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler,
ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler,
},
new GenerateToolboxGroup(),
FreehandSliderToolboxGroup
}
);
Expand Down
193 changes: 193 additions & 0 deletions osu.Game.Rulesets.Osu/Edit/PolygonGenerationPopover.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. 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.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;

namespace osu.Game.Rulesets.Osu.Edit
{
public partial class PolygonGenerationPopover : OsuPopover
{
private SliderWithTextBoxInput<double> distanceSnapInput = null!;
private SliderWithTextBoxInput<int> offsetAngleInput = null!;
private SliderWithTextBoxInput<int> repeatCountInput = null!;
private SliderWithTextBoxInput<int> pointInput = null!;
private RoundedButton commitButton = null!;

private readonly List<HitCircle> insertedCircles = new List<HitCircle>();
private bool began;
private bool committed;

[Resolved]
private IBeatSnapProvider beatSnapProvider { get; set; } = null!;

[Resolved]
private EditorClock editorClock { get; set; } = null!;

[Resolved]
private EditorBeatmap editorBeatmap { get; set; } = null!;

[Resolved]
private IEditorChangeHandler? changeHandler { get; set; }

[Resolved]
private HitObjectComposer composer { get; set; } = null!;

[BackgroundDependencyLoader]
private void load()
{
Child = new FillFlowContainer
{
Width = 220,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(20),
Children = new Drawable[]
{
distanceSnapInput = new SliderWithTextBoxInput<double>("Distance snap:")
{
Current = new BindableNumber<double>(1)
{
MinValue = 0.1,
MaxValue = 6,
Precision = 0.1,
Value = ((OsuHitObjectComposer)composer).DistanceSnapProvider.DistanceSpacingMultiplier.Value,
},
Instantaneous = true
},
offsetAngleInput = new SliderWithTextBoxInput<int>("Offset angle:")
{
Current = new BindableNumber<int>
{
MinValue = 0,
MaxValue = 180,
Precision = 1
},
Instantaneous = true
},
repeatCountInput = new SliderWithTextBoxInput<int>("Repeats:")
{
Current = new BindableNumber<int>(1)
{
MinValue = 1,
MaxValue = 10,
Precision = 1
},
Instantaneous = true
},
pointInput = new SliderWithTextBoxInput<int>("Vertices:")
{
Current = new BindableNumber<int>(3)
{
MinValue = 3,
MaxValue = 10,
Precision = 1,
},
Instantaneous = true
},
commitButton = new RoundedButton
{
RelativeSizeAxes = Axes.X,
Text = "Create",
Action = commit
}
}
};
}

protected override void LoadComplete()
{
base.LoadComplete();

changeHandler?.BeginChange();
began = true;

distanceSnapInput.Current.BindValueChanged(_ => tryCreatePolygon());
offsetAngleInput.Current.BindValueChanged(_ => tryCreatePolygon());
repeatCountInput.Current.BindValueChanged(_ => tryCreatePolygon());
pointInput.Current.BindValueChanged(_ => tryCreatePolygon());
tryCreatePolygon();
}

private void tryCreatePolygon()
{
double startTime = beatSnapProvider.SnapTime(editorClock.CurrentTime);
TimingControlPoint timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(startTime);
double timeSpacing = timingPoint.BeatLength / editorBeatmap.BeatDivisor;
IHasSliderVelocity lastWithSliderVelocity = editorBeatmap.HitObjects.Where(ho => ho.GetEndTime() <= startTime).OfType<IHasSliderVelocity>().LastOrDefault() ?? new Slider();
double velocity = OsuHitObject.BASE_SCORING_DISTANCE * editorBeatmap.Difficulty.SliderMultiplier
/ LegacyRulesetExtensions.GetPrecisionAdjustedBeatLength(lastWithSliderVelocity, timingPoint, OsuRuleset.SHORT_NAME);
double length = distanceSnapInput.Current.Value * velocity * timeSpacing;
float polygonRadius = (float)(length / (2 * Math.Sin(double.Pi / pointInput.Current.Value)));

editorBeatmap.RemoveRange(insertedCircles);
insertedCircles.Clear();

var selectionHandler = (EditorSelectionHandler)composer.BlueprintContainer.SelectionHandler;
bool first = true;

for (int i = 1; i <= pointInput.Current.Value * repeatCountInput.Current.Value; ++i)
{
float angle = float.DegreesToRadians(offsetAngleInput.Current.Value) + i * (2 * float.Pi / pointInput.Current.Value);
var position = OsuPlayfield.BASE_SIZE / 2 + new Vector2(polygonRadius * float.Cos(angle), polygonRadius * float.Sin(angle));

var circle = new HitCircle
{
Position = position,
StartTime = startTime,
NewCombo = first && selectionHandler.SelectionNewComboState.Value == TernaryState.True,
};
// TODO: probably ensure samples also follow current ternary status (not trivial)
circle.Samples.Add(circle.CreateHitSampleInfo());

if (position.X < 0 || position.Y < 0 || position.X > OsuPlayfield.BASE_SIZE.X || position.Y > OsuPlayfield.BASE_SIZE.Y)
{
commitButton.Enabled.Value = false;
return;
}

insertedCircles.Add(circle);
startTime = beatSnapProvider.SnapTime(startTime + timeSpacing);

first = false;
}

editorBeatmap.AddRange(insertedCircles);
commitButton.Enabled.Value = true;
}

private void commit()
{
changeHandler?.EndChange();
committed = true;
Hide();
}

protected override void PopOut()
{
base.PopOut();

if (began && !committed)
{
editorBeatmap.RemoveRange(insertedCircles);
changeHandler?.EndChange();
}
}
}
}

0 comments on commit 510f2c8

Please sign in to comment.