Skip to content

Commit 225b309

Browse files
committed
Reimplement stable polygon tool
Addresses ppy#19970. While yes, ppy#26303 is also a thing, in discussing with users I don't think that grids are going to be able to deprecate this feature. Logic transcribed verbatim from stable.
1 parent cd4dce2 commit 225b309

File tree

3 files changed

+249
-1
lines changed

3 files changed

+249
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
2+
// See the LICENCE file in the repository root for full licence text.
3+
4+
using osu.Framework.Graphics;
5+
using osu.Framework.Graphics.Containers;
6+
using osu.Framework.Graphics.Sprites;
7+
using osu.Framework.Input.Events;
8+
using osu.Game.Rulesets.Edit;
9+
using osu.Game.Screens.Edit.Components;
10+
using osuTK;
11+
using osuTK.Input;
12+
13+
namespace osu.Game.Rulesets.Osu.Edit
14+
{
15+
public partial class GenerateToolboxGroup : EditorToolboxGroup
16+
{
17+
private readonly EditorToolButton polygonButton;
18+
19+
public GenerateToolboxGroup()
20+
: base("Generate")
21+
{
22+
Child = new FillFlowContainer
23+
{
24+
RelativeSizeAxes = Axes.X,
25+
AutoSizeAxes = Axes.Y,
26+
Spacing = new Vector2(5),
27+
Children = new Drawable[]
28+
{
29+
polygonButton = new EditorToolButton("Polygon",
30+
() => new SpriteIcon { Icon = FontAwesome.Solid.Spinner },
31+
() => new PolygonGenerationPopover()),
32+
}
33+
};
34+
}
35+
36+
protected override bool OnKeyDown(KeyDownEvent e)
37+
{
38+
if (e.Repeat) return false;
39+
40+
switch (e.Key)
41+
{
42+
case Key.D:
43+
if (!e.ControlPressed || !e.ShiftPressed)
44+
return false;
45+
46+
polygonButton.TriggerClick();
47+
return true;
48+
49+
default:
50+
return false;
51+
}
52+
}
53+
}
54+
}

osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ protected override IEnumerable<TernaryButton> CreateTernaryButtons()
6464
private Bindable<HitObject> placementObject;
6565

6666
[Cached(typeof(IDistanceSnapProvider))]
67-
protected readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
67+
public readonly OsuDistanceSnapProvider DistanceSnapProvider = new OsuDistanceSnapProvider();
6868

6969
[Cached]
7070
protected readonly OsuGridToolboxGroup OsuGridToolboxGroup = new OsuGridToolboxGroup();
@@ -109,6 +109,7 @@ private void load()
109109
RotationHandler = BlueprintContainer.SelectionHandler.RotationHandler,
110110
ScaleHandler = (OsuSelectionScaleHandler)BlueprintContainer.SelectionHandler.ScaleHandler,
111111
},
112+
new GenerateToolboxGroup(),
112113
FreehandlSliderToolboxGroup
113114
}
114115
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
2+
// See the LICENCE file in the repository root for full licence text.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Linq;
7+
using osu.Framework.Allocation;
8+
using osu.Framework.Bindables;
9+
using osu.Framework.Graphics;
10+
using osu.Framework.Graphics.Containers;
11+
using osu.Game.Beatmaps.ControlPoints;
12+
using osu.Game.Graphics.UserInterface;
13+
using osu.Game.Graphics.UserInterfaceV2;
14+
using osu.Game.Rulesets.Edit;
15+
using osu.Game.Rulesets.Objects;
16+
using osu.Game.Rulesets.Objects.Legacy;
17+
using osu.Game.Rulesets.Objects.Types;
18+
using osu.Game.Rulesets.Osu.Objects;
19+
using osu.Game.Rulesets.Osu.UI;
20+
using osu.Game.Screens.Edit;
21+
using osu.Game.Screens.Edit.Compose.Components;
22+
using osuTK;
23+
24+
namespace osu.Game.Rulesets.Osu.Edit
25+
{
26+
public partial class PolygonGenerationPopover : OsuPopover
27+
{
28+
private SliderWithTextBoxInput<double> distanceSnapInput = null!;
29+
private SliderWithTextBoxInput<int> offsetAngleInput = null!;
30+
private SliderWithTextBoxInput<int> repeatCountInput = null!;
31+
private SliderWithTextBoxInput<int> pointInput = null!;
32+
private RoundedButton commitButton = null!;
33+
34+
private readonly List<HitCircle> insertedCircles = new List<HitCircle>();
35+
private bool began;
36+
private bool committed;
37+
38+
[Resolved]
39+
private IBeatSnapProvider beatSnapProvider { get; set; } = null!;
40+
41+
[Resolved]
42+
private EditorClock editorClock { get; set; } = null!;
43+
44+
[Resolved]
45+
private EditorBeatmap editorBeatmap { get; set; } = null!;
46+
47+
[Resolved]
48+
private IEditorChangeHandler? changeHandler { get; set; }
49+
50+
[Resolved]
51+
private HitObjectComposer composer { get; set; } = null!;
52+
53+
[BackgroundDependencyLoader]
54+
private void load()
55+
{
56+
Child = new FillFlowContainer
57+
{
58+
Width = 220,
59+
AutoSizeAxes = Axes.Y,
60+
Spacing = new Vector2(20),
61+
Children = new Drawable[]
62+
{
63+
distanceSnapInput = new SliderWithTextBoxInput<double>("Distance snap:")
64+
{
65+
Current = new BindableNumber<double>(1)
66+
{
67+
MinValue = 0.1,
68+
MaxValue = 6,
69+
Precision = 0.1,
70+
Value = ((OsuHitObjectComposer)composer).DistanceSnapProvider.DistanceSpacingMultiplier.Value,
71+
},
72+
Instantaneous = true
73+
},
74+
offsetAngleInput = new SliderWithTextBoxInput<int>("Offset angle:")
75+
{
76+
Current = new BindableNumber<int>
77+
{
78+
MinValue = 0,
79+
MaxValue = 180,
80+
Precision = 1
81+
},
82+
Instantaneous = true
83+
},
84+
repeatCountInput = new SliderWithTextBoxInput<int>("Repeats:")
85+
{
86+
Current = new BindableNumber<int>(1)
87+
{
88+
MinValue = 1,
89+
MaxValue = 10,
90+
Precision = 1
91+
},
92+
Instantaneous = true
93+
},
94+
pointInput = new SliderWithTextBoxInput<int>("Vertices:")
95+
{
96+
Current = new BindableNumber<int>(3)
97+
{
98+
MinValue = 3,
99+
MaxValue = 10,
100+
Precision = 1,
101+
},
102+
Instantaneous = true
103+
},
104+
commitButton = new RoundedButton
105+
{
106+
RelativeSizeAxes = Axes.X,
107+
Text = "Create",
108+
Action = commit
109+
}
110+
}
111+
};
112+
}
113+
114+
protected override void LoadComplete()
115+
{
116+
base.LoadComplete();
117+
118+
changeHandler?.BeginChange();
119+
began = true;
120+
121+
distanceSnapInput.Current.BindValueChanged(_ => tryCreatePolygon());
122+
offsetAngleInput.Current.BindValueChanged(_ => tryCreatePolygon());
123+
repeatCountInput.Current.BindValueChanged(_ => tryCreatePolygon());
124+
pointInput.Current.BindValueChanged(_ => tryCreatePolygon());
125+
tryCreatePolygon();
126+
}
127+
128+
private void tryCreatePolygon()
129+
{
130+
double startTime = beatSnapProvider.SnapTime(editorClock.CurrentTime);
131+
TimingControlPoint timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(startTime);
132+
double timeSpacing = timingPoint.BeatLength / editorBeatmap.BeatDivisor;
133+
IHasSliderVelocity lastWithSliderVelocity = editorBeatmap.HitObjects.Where(ho => ho.GetEndTime() <= startTime).OfType<IHasSliderVelocity>().LastOrDefault() ?? new Slider();
134+
double velocity = OsuHitObject.BASE_SCORING_DISTANCE * editorBeatmap.Difficulty.SliderMultiplier
135+
/ LegacyRulesetExtensions.GetPrecisionAdjustedBeatLength(lastWithSliderVelocity, timingPoint, OsuRuleset.SHORT_NAME);
136+
double length = distanceSnapInput.Current.Value * velocity * timeSpacing;
137+
float polygonRadius = (float)(length / (2 * Math.Sin(double.Pi / pointInput.Current.Value)));
138+
139+
editorBeatmap.RemoveRange(insertedCircles);
140+
insertedCircles.Clear();
141+
142+
var selectionHandler = (EditorSelectionHandler)composer.BlueprintContainer.SelectionHandler;
143+
bool first = true;
144+
145+
for (int i = 1; i <= pointInput.Current.Value * repeatCountInput.Current.Value; ++i)
146+
{
147+
float angle = float.DegreesToRadians(offsetAngleInput.Current.Value) + i * (2 * float.Pi / pointInput.Current.Value);
148+
var position = OsuPlayfield.BASE_SIZE / 2 + new Vector2(polygonRadius * float.Cos(angle), polygonRadius * float.Sin(angle));
149+
150+
var circle = new HitCircle
151+
{
152+
Position = position,
153+
StartTime = startTime,
154+
NewCombo = first && selectionHandler.SelectionNewComboState.Value == TernaryState.True,
155+
};
156+
// TODO: probably ensure samples also follow current ternary status (not trivial)
157+
circle.Samples.Add(circle.CreateHitSampleInfo());
158+
159+
if (position.X < 0 || position.Y < 0 || position.X > OsuPlayfield.BASE_SIZE.X || position.Y > OsuPlayfield.BASE_SIZE.Y)
160+
{
161+
commitButton.Enabled.Value = false;
162+
return;
163+
}
164+
165+
insertedCircles.Add(circle);
166+
startTime = beatSnapProvider.SnapTime(startTime + timeSpacing);
167+
168+
first = false;
169+
}
170+
171+
editorBeatmap.AddRange(insertedCircles);
172+
commitButton.Enabled.Value = true;
173+
}
174+
175+
private void commit()
176+
{
177+
changeHandler?.EndChange();
178+
committed = true;
179+
Hide();
180+
}
181+
182+
protected override void PopOut()
183+
{
184+
base.PopOut();
185+
186+
if (began && !committed)
187+
{
188+
editorBeatmap.RemoveRange(insertedCircles);
189+
changeHandler?.EndChange();
190+
}
191+
}
192+
}
193+
}

0 commit comments

Comments
 (0)