Skip to content

Commit

Permalink
Merge pull request #6290 from EVAST9919/hacky-circle
Browse files Browse the repository at this point in the history
Implement `FastCircle` component
  • Loading branch information
smoogipoo authored Aug 30, 2024
2 parents a4a2b68 + 2937678 commit b281542
Show file tree
Hide file tree
Showing 6 changed files with 402 additions and 0 deletions.
188 changes: 188 additions & 0 deletions osu.Framework.Tests/Visual/Drawables/TestSceneFastCircle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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();
}
}
131 changes: 131 additions & 0 deletions osu.Framework/Graphics/Shapes/FastCircle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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();
}
}
}
}
Loading

0 comments on commit b281542

Please sign in to comment.