-
Notifications
You must be signed in to change notification settings - Fork 420
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6290 from EVAST9919/hacky-circle
Implement `FastCircle` component
- Loading branch information
Showing
6 changed files
with
402 additions
and
0 deletions.
There are no files selected for viewing
188 changes: 188 additions & 0 deletions
188
osu.Framework.Tests/Visual/Drawables/TestSceneFastCircle.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
// 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 NUnit.Framework; | ||
using osu.Framework.Graphics.Shapes; | ||
using osu.Framework.Graphics; | ||
using osu.Framework.Graphics.Containers; | ||
using osu.Framework.Graphics.Sprites; | ||
using osu.Framework.Input.Events; | ||
using osu.Framework.Testing; | ||
using osuTK; | ||
using osuTK.Input; | ||
|
||
namespace osu.Framework.Tests.Visual.Drawables | ||
{ | ||
public partial class TestSceneFastCircle : ManualInputManagerTestScene | ||
{ | ||
private TestCircle fastCircle = null!; | ||
private Circle circle = null!; | ||
private CircularContainer fastCircleMask = null!; | ||
private CircularContainer circleMask = null!; | ||
|
||
[SetUp] | ||
public void Setup() => Schedule(() => | ||
{ | ||
Child = new GridContainer | ||
{ | ||
RelativeSizeAxes = Axes.Both, | ||
RowDimensions = new[] | ||
{ | ||
new Dimension(GridSizeMode.Absolute, 100), | ||
new Dimension(), | ||
}, | ||
ColumnDimensions = new[] | ||
{ | ||
new Dimension(GridSizeMode.Relative, 0.5f), | ||
new Dimension() | ||
}, | ||
Content = new[] | ||
{ | ||
new Drawable[] | ||
{ | ||
new SpriteText | ||
{ | ||
Anchor = Anchor.Centre, | ||
Origin = Anchor.Centre, | ||
Text = "FastCircle" | ||
}, | ||
new SpriteText | ||
{ | ||
Anchor = Anchor.Centre, | ||
Origin = Anchor.Centre, | ||
Text = "Circle" | ||
} | ||
}, | ||
new Drawable[] | ||
{ | ||
fastCircleMask = new CircularContainer | ||
{ | ||
Anchor = Anchor.Centre, | ||
Origin = Anchor.TopRight, | ||
Size = new Vector2(200), | ||
Child = fastCircle = new TestCircle | ||
{ | ||
Anchor = Anchor.TopRight, | ||
Origin = Anchor.Centre, | ||
Size = new Vector2(200), | ||
Clicked = onClick | ||
} | ||
}, | ||
circleMask = new CircularContainer | ||
{ | ||
Anchor = Anchor.Centre, | ||
Origin = Anchor.TopRight, | ||
Size = new Vector2(200), | ||
Child = circle = new Circle | ||
{ | ||
Anchor = Anchor.TopRight, | ||
Origin = Anchor.Centre, | ||
Size = new Vector2(200) | ||
} | ||
}, | ||
} | ||
} | ||
}; | ||
}); | ||
|
||
[Test] | ||
public void TestInput() | ||
{ | ||
testInput(new Vector2(200, 100)); | ||
testInput(new Vector2(100, 200)); | ||
testInput(new Vector2(200, 200)); | ||
} | ||
|
||
[Test] | ||
public void TestSmoothness() | ||
{ | ||
AddStep("Change smoothness to 0", () => fastCircle.EdgeSmoothness = circle.MaskingSmoothness = 0); | ||
AddStep("Change smoothness to 1", () => fastCircle.EdgeSmoothness = circle.MaskingSmoothness = 1); | ||
AddStep("Change smoothness to 5", () => fastCircle.EdgeSmoothness = circle.MaskingSmoothness = 5); | ||
} | ||
|
||
[Test] | ||
public void TestNestedMasking() | ||
{ | ||
AddToggleStep("Toggle parent masking", m => fastCircleMask.Masking = circleMask.Masking = m); | ||
} | ||
|
||
[Test] | ||
public void TestRotation() | ||
{ | ||
resize(new Vector2(200, 100)); | ||
AddToggleStep("Toggle rotation", rotate => | ||
{ | ||
fastCircle.ClearTransforms(); | ||
circle.ClearTransforms(); | ||
|
||
if (rotate) | ||
{ | ||
fastCircle.Spin(2000, RotationDirection.Clockwise); | ||
circle.Spin(2000, RotationDirection.Clockwise); | ||
} | ||
}); | ||
} | ||
|
||
[Test] | ||
public void TestShear() | ||
{ | ||
resize(new Vector2(200, 100)); | ||
AddToggleStep("Toggle shear", shear => | ||
{ | ||
fastCircle.Shear = circle.Shear = shear ? new Vector2(0.5f, 0) : Vector2.Zero; | ||
}); | ||
} | ||
|
||
[Test] | ||
public void TestScale() | ||
{ | ||
resize(new Vector2(200, 100)); | ||
AddToggleStep("Toggle scale", scale => | ||
{ | ||
fastCircle.Scale = circle.Scale = scale ? new Vector2(2f, 1f) : Vector2.One; | ||
}); | ||
} | ||
|
||
private void testInput(Vector2 size) | ||
{ | ||
resize(size); | ||
AddStep("Click outside the corner", () => clickNearCorner(-Vector2.One)); | ||
AddAssert("input not received", () => clicked == false); | ||
AddStep("Click inside the corner", () => clickNearCorner(Vector2.One)); | ||
AddAssert("input received", () => clicked); | ||
} | ||
|
||
private void resize(Vector2 size) | ||
{ | ||
AddStep($"Resize to {size}", () => | ||
{ | ||
fastCircle.Size = circle.Size = size; | ||
}); | ||
} | ||
|
||
private void clickNearCorner(Vector2 offset) | ||
{ | ||
clicked = false; | ||
InputManager.MoveMouseTo(fastCircle.ToScreenSpace(new Vector2(fastCircle.Radius * (1f - MathF.Sqrt(0.5f))) + offset)); | ||
InputManager.Click(MouseButton.Left); | ||
} | ||
|
||
private bool clicked; | ||
|
||
private void onClick() => clicked = true; | ||
|
||
private partial class TestCircle : FastCircle | ||
{ | ||
public Action? Clicked; | ||
|
||
protected override bool OnClick(ClickEvent e) | ||
{ | ||
base.OnClick(e); | ||
Clicked?.Invoke(); | ||
return true; | ||
} | ||
} | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
osu.Framework.Tests/Visual/Performance/TestSceneCircleBoxAlternatePerformance.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// 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.Shapes; | ||
|
||
namespace osu.Framework.Tests.Visual.Performance | ||
{ | ||
public sealed partial class TestSceneCircleBoxAlternatePerformance : RepeatedDrawablePerformanceTestScene | ||
{ | ||
private int index; | ||
|
||
protected override Drawable CreateDrawable() | ||
{ | ||
index++; | ||
if (index % 2 == 0) | ||
return new Circle(); | ||
|
||
return new Box(); | ||
} | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
osu.Framework.Tests/Visual/Performance/TestSceneFastCircleBoxAlternatePerformance.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// 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.Shapes; | ||
|
||
namespace osu.Framework.Tests.Visual.Performance | ||
{ | ||
public sealed partial class TestSceneFastCircleBoxAlternatePerformance : RepeatedDrawablePerformanceTestScene | ||
{ | ||
private int index; | ||
|
||
protected override Drawable CreateDrawable() | ||
{ | ||
index++; | ||
if (index % 2 == 0) | ||
return new FastCircle(); | ||
|
||
return new Box(); | ||
} | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
osu.Framework.Tests/Visual/Performance/TestSceneFastCirclePerformance.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// 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.Shapes; | ||
|
||
namespace osu.Framework.Tests.Visual.Performance | ||
{ | ||
public sealed partial class TestSceneFastCirclePerformance : RepeatedDrawablePerformanceTestScene | ||
{ | ||
protected override Drawable CreateDrawable() => new FastCircle(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
// 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 osu.Framework.Allocation; | ||
using osu.Framework.Graphics.Primitives; | ||
using osu.Framework.Graphics.Rendering; | ||
using osu.Framework.Graphics.Rendering.Vertices; | ||
using osu.Framework.Graphics.Shaders; | ||
using osuTK; | ||
|
||
namespace osu.Framework.Graphics.Shapes | ||
{ | ||
/// <summary> | ||
/// A circle that is rendered directly to the screen using a specialised shader. | ||
/// This behaves slightly differently from <see cref="Circle"/> but offers | ||
/// higher performance in scenarios where many circles are drawn at once. | ||
/// </summary> | ||
public partial class FastCircle : Drawable | ||
{ | ||
private float edgeSmoothness = 1f; | ||
|
||
public float EdgeSmoothness | ||
{ | ||
get => edgeSmoothness; | ||
set | ||
{ | ||
if (edgeSmoothness == value) | ||
return; | ||
|
||
edgeSmoothness = value; | ||
|
||
if (IsLoaded) | ||
Invalidate(Invalidation.DrawNode); | ||
} | ||
} | ||
|
||
private IShader shader = null!; | ||
|
||
[BackgroundDependencyLoader] | ||
private void load(ShaderManager shaders) | ||
{ | ||
shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, "FastCircle"); | ||
} | ||
|
||
public float Radius => MathF.Min(DrawSize.X, DrawSize.Y) * 0.5f; | ||
|
||
public override bool Contains(Vector2 screenSpacePos) | ||
{ | ||
if (!base.Contains(screenSpacePos)) | ||
return false; | ||
|
||
float cRadius = Radius; | ||
return DrawRectangle.Shrink(cRadius).DistanceExponentiated(ToLocalSpace(screenSpacePos), 2f) <= cRadius * cRadius; | ||
} | ||
|
||
protected override DrawNode CreateDrawNode() => new FastCircleDrawNode(this); | ||
|
||
private class FastCircleDrawNode : DrawNode | ||
{ | ||
protected new FastCircle Source => (FastCircle)base.Source; | ||
|
||
public FastCircleDrawNode(FastCircle source) | ||
: base(source) | ||
{ | ||
} | ||
|
||
private Quad screenSpaceDrawQuad; | ||
private Vector4 drawRectangle; | ||
private Vector2 blend; | ||
private IShader shader = null!; | ||
|
||
public override void ApplyState() | ||
{ | ||
base.ApplyState(); | ||
|
||
screenSpaceDrawQuad = Source.ScreenSpaceDrawQuad; | ||
drawRectangle = new Vector4(0, 0, Source.DrawWidth, Source.DrawHeight); | ||
shader = Source.shader; | ||
blend = new Vector2(Source.edgeSmoothness * Math.Min(Source.DrawWidth, Source.DrawHeight) / Math.Min(screenSpaceDrawQuad.Width, screenSpaceDrawQuad.Height)); | ||
} | ||
|
||
protected override void Draw(IRenderer renderer) | ||
{ | ||
base.Draw(renderer); | ||
|
||
if (!renderer.BindTexture(renderer.WhitePixel)) | ||
return; | ||
|
||
shader.Bind(); | ||
|
||
var vertexAction = renderer.DefaultQuadBatch.AddAction; | ||
|
||
vertexAction(new TexturedVertex2D(renderer) | ||
{ | ||
Position = screenSpaceDrawQuad.BottomLeft, | ||
TexturePosition = new Vector2(0, drawRectangle.W), | ||
TextureRect = drawRectangle, | ||
BlendRange = blend, | ||
Colour = DrawColourInfo.Colour.BottomLeft.SRGB, | ||
}); | ||
vertexAction(new TexturedVertex2D(renderer) | ||
{ | ||
Position = screenSpaceDrawQuad.BottomRight, | ||
TexturePosition = new Vector2(drawRectangle.Z, drawRectangle.W), | ||
TextureRect = drawRectangle, | ||
BlendRange = blend, | ||
Colour = DrawColourInfo.Colour.BottomRight.SRGB, | ||
}); | ||
vertexAction(new TexturedVertex2D(renderer) | ||
{ | ||
Position = screenSpaceDrawQuad.TopRight, | ||
TexturePosition = new Vector2(drawRectangle.Z, 0), | ||
TextureRect = drawRectangle, | ||
BlendRange = blend, | ||
Colour = DrawColourInfo.Colour.TopRight.SRGB, | ||
}); | ||
vertexAction(new TexturedVertex2D(renderer) | ||
{ | ||
Position = screenSpaceDrawQuad.TopLeft, | ||
TexturePosition = Vector2.Zero, | ||
TextureRect = drawRectangle, | ||
BlendRange = blend, | ||
Colour = DrawColourInfo.Colour.TopLeft.SRGB, | ||
}); | ||
|
||
shader.Unbind(); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.