forked from ppy/osu-framework
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Wip implementation of QuadTree for Path input
- Loading branch information
Showing
3 changed files
with
357 additions
and
10 deletions.
There are no files selected for viewing
104 changes: 104 additions & 0 deletions
104
osu.Framework.Tests/Visual/Graphics/TestSceneQuadTree.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,104 @@ | ||
// 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.Containers; | ||
using osu.Framework.Graphics.Primitives; | ||
using osu.Framework.Graphics.Shapes; | ||
using osu.Framework.Input.Events; | ||
using osu.Framework.Utils; | ||
using osuTK; | ||
using osuTK.Graphics; | ||
using osuTK.Input; | ||
|
||
namespace osu.Framework.Tests.Visual.Graphics | ||
{ | ||
public partial class TestSceneQuadTree : FrameworkTestScene | ||
{ | ||
private readonly QuadTree quadTree; | ||
private readonly Container boxes; | ||
private readonly Container points; | ||
|
||
public TestSceneQuadTree() | ||
{ | ||
quadTree = new QuadTree(new RectangleF(0, 0, 800, 600)); | ||
|
||
Child = new Container | ||
{ | ||
Anchor = Anchor.Centre, | ||
Origin = Anchor.Centre, | ||
Size = new Vector2(800, 600), | ||
Children = new Drawable[] | ||
{ | ||
new Box | ||
{ | ||
RelativeSizeAxes = Axes.Both, | ||
Alpha = 0.2f, | ||
}, | ||
boxes = new Container | ||
{ | ||
RelativeSizeAxes = Axes.Both | ||
}, | ||
points = new Container | ||
{ | ||
RelativeSizeAxes = Axes.Both | ||
} | ||
} | ||
}; | ||
} | ||
|
||
protected override bool OnMouseDown(MouseDownEvent e) | ||
{ | ||
if (e.Button != MouseButton.Left) | ||
return base.OnMouseDown(e); | ||
|
||
Vector2 localPos = points.ToLocalSpace(e.ScreenSpaceMouseDownPosition); | ||
|
||
quadTree.Insert(localPos); | ||
|
||
points.Add(new Circle | ||
{ | ||
Origin = Anchor.Centre, | ||
Size = new Vector2(12), | ||
Colour = Color4.Yellow, | ||
Position = localPos | ||
}); | ||
|
||
boxes.Clear(); | ||
|
||
foreach (var area in quadTree.EnumerateAreas()) | ||
{ | ||
boxes.Add(new Container | ||
{ | ||
Position = area.Location, | ||
Size = area.Size, | ||
Masking = true, | ||
BorderColour = Color4.White, | ||
BorderThickness = 2, | ||
Child = new Box | ||
{ | ||
RelativeSizeAxes = Axes.Both, | ||
Alpha = 0, | ||
AlwaysPresent = true | ||
} | ||
}); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
protected override bool OnMouseMove(MouseMoveEvent e) | ||
{ | ||
if (!e.IsPressed(MouseButton.Right)) | ||
return base.OnMouseMove(e); | ||
|
||
if (quadTree.TryGetClosest(points.ToLocalSpace(e.ScreenSpaceMousePosition), out Vector2 closest)) | ||
{ | ||
foreach (var p in points) | ||
p.Colour = Precision.AlmostEquals(p.Position, closest) ? Color4.Blue : Color4.Yellow; | ||
} | ||
|
||
return true; | ||
} | ||
} | ||
} |
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
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,233 @@ | ||
// 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 System.Collections.Generic; | ||
using System.Diagnostics; | ||
using osu.Framework.Graphics.Primitives; | ||
using osuTK; | ||
|
||
namespace osu.Framework.Graphics | ||
{ | ||
public class QuadTree | ||
{ | ||
private readonly QuadTreeNode rootNode; | ||
|
||
public QuadTree(RectangleF area) | ||
{ | ||
rootNode = new QuadTreeNode(null, area); | ||
} | ||
|
||
public bool Insert(Vector2 point) => rootNode.Insert(point); | ||
|
||
public bool TryGetClosest(Vector2 point, out Vector2 closest) => rootNode.TryGetClosest(point, out closest); | ||
|
||
public IEnumerable<RectangleF> EnumerateAreas() => rootNode.EnumerateAreas(); | ||
|
||
private class QuadTreeNode | ||
{ | ||
private const int capacity = 4; | ||
|
||
public readonly RectangleF Area; | ||
private readonly QuadTreeNode? parent; | ||
|
||
private List<Vector2>? points = new List<Vector2>(capacity); | ||
private QuadTreeNode? topLeft; | ||
private QuadTreeNode? topRight; | ||
private QuadTreeNode? bottomLeft; | ||
private QuadTreeNode? bottomRight; | ||
|
||
public QuadTreeNode(QuadTreeNode? parent, RectangleF area) | ||
{ | ||
this.parent = parent; | ||
Area = area; | ||
} | ||
|
||
public bool Insert(Vector2 point) | ||
{ | ||
// ReSharper disable once PossiblyImpureMethodCallOnReadonlyVariable | ||
if (!Area.Contains(point)) | ||
return false; | ||
|
||
if (points?.Count == capacity) | ||
subDivide(); | ||
|
||
if (points != null) | ||
{ | ||
points.Add(point); | ||
return true; | ||
} | ||
|
||
Debug.Assert(topLeft != null); | ||
Debug.Assert(topRight != null); | ||
Debug.Assert(bottomLeft != null); | ||
Debug.Assert(bottomRight != null); | ||
|
||
return topLeft.Insert(point) | ||
|| topRight.Insert(point) | ||
|| bottomLeft.Insert(point) | ||
|| bottomRight.Insert(point); | ||
} | ||
|
||
public bool TryGetClosest(Vector2 point, out Vector2 closest) | ||
{ | ||
QuadTreeNode? closestNode = findContainingNode(point); | ||
|
||
if (closestNode == null) | ||
{ | ||
closest = default; | ||
return false; | ||
} | ||
|
||
Debug.Assert(closestNode.points != null); | ||
|
||
float distToLeft = MathF.Abs(point.X - closestNode.Area.Left); | ||
float distToRight = MathF.Abs(point.X - closestNode.Area.Right); | ||
float distToTop = MathF.Abs(point.Y - closestNode.Area.Top); | ||
float distToBottom = MathF.Abs(point.Y - closestNode.Area.Bottom); | ||
float distToClosestBoundary = MathF.Min(MathF.Min(MathF.Min(distToLeft, distToRight), distToTop), distToBottom); | ||
|
||
Vector2 closestPoint = Vector2.Zero; | ||
float distToClosestPoint = float.MaxValue; | ||
|
||
foreach (var pt in closestNode.points) | ||
computeDistanceTo(pt); | ||
|
||
// We're closer to the point than to any boundary of this node. | ||
if (closestNode.parent == null || distToClosestBoundary >= distToClosestPoint) | ||
{ | ||
closest = closestPoint; | ||
return true; | ||
} | ||
|
||
// We're closer to the boundary than to any point in this node. | ||
// We need to check the neighbouring boundaries to see if there's any point closer. | ||
foreach (var pt in closestNode.parent.enumeratePoints(closestNode)) | ||
computeDistanceTo(pt); | ||
|
||
closest = closestPoint; | ||
return true; | ||
|
||
void computeDistanceTo(Vector2 pt) | ||
{ | ||
float dist = (point - pt).Length; | ||
|
||
if (dist < distToClosestPoint) | ||
{ | ||
distToClosestPoint = dist; | ||
closestPoint = pt; | ||
} | ||
} | ||
} | ||
|
||
private QuadTreeNode? findContainingNode(Vector2 point) | ||
{ | ||
// ReSharper disable once PossiblyImpureMethodCallOnReadonlyVariable | ||
if (!Area.Contains(point)) | ||
return null; | ||
|
||
if (points != null) | ||
return this; | ||
|
||
Debug.Assert(topLeft != null); | ||
Debug.Assert(topRight != null); | ||
Debug.Assert(bottomLeft != null); | ||
Debug.Assert(bottomRight != null); | ||
|
||
return topLeft.findContainingNode(point) | ||
?? topRight.findContainingNode(point) | ||
?? bottomLeft.findContainingNode(point) | ||
?? bottomRight.findContainingNode(point); | ||
} | ||
|
||
public IEnumerable<RectangleF> EnumerateAreas() | ||
{ | ||
yield return Area; | ||
|
||
if (topLeft != null) | ||
{ | ||
foreach (var area in topLeft.EnumerateAreas()) | ||
yield return area; | ||
} | ||
|
||
if (topRight != null) | ||
{ | ||
foreach (var area in topRight.EnumerateAreas()) | ||
yield return area; | ||
} | ||
|
||
if (bottomLeft != null) | ||
{ | ||
foreach (var area in bottomLeft.EnumerateAreas()) | ||
yield return area; | ||
} | ||
|
||
if (bottomRight != null) | ||
{ | ||
foreach (var area in bottomRight.EnumerateAreas()) | ||
yield return area; | ||
} | ||
} | ||
|
||
private IEnumerable<Vector2> enumeratePoints(QuadTreeNode? exceptNode) | ||
{ | ||
if (points != null) | ||
{ | ||
foreach (var pt in points) | ||
yield return pt; | ||
|
||
yield break; | ||
} | ||
|
||
Debug.Assert(topLeft != null); | ||
Debug.Assert(topRight != null); | ||
Debug.Assert(bottomLeft != null); | ||
Debug.Assert(bottomRight != null); | ||
|
||
if (topLeft != exceptNode) | ||
{ | ||
foreach (var pt in topLeft.enumeratePoints(exceptNode)) | ||
yield return pt; | ||
} | ||
|
||
if (topRight != exceptNode) | ||
{ | ||
foreach (var pt in topRight.enumeratePoints(exceptNode)) | ||
yield return pt; | ||
} | ||
|
||
if (bottomLeft != exceptNode) | ||
{ | ||
foreach (var pt in bottomLeft.enumeratePoints(exceptNode)) | ||
yield return pt; | ||
} | ||
|
||
if (bottomRight != exceptNode) | ||
{ | ||
foreach (var pt in bottomRight.enumeratePoints(exceptNode)) | ||
yield return pt; | ||
} | ||
} | ||
|
||
private void subDivide() | ||
{ | ||
Debug.Assert(points != null); | ||
|
||
topLeft = new QuadTreeNode(this, new RectangleF(Area.Location, Area.Size / 2)); | ||
topRight = new QuadTreeNode(this, new RectangleF(new Vector2(Area.Centre.X, Area.Y), Area.Size / 2)); | ||
bottomLeft = new QuadTreeNode(this, new RectangleF(new Vector2(Area.X, Area.Centre.Y), Area.Size / 2)); | ||
bottomRight = new QuadTreeNode(this, new RectangleF(Area.Centre, Area.Size / 2)); | ||
|
||
foreach (var p in points) | ||
{ | ||
bool _ = topLeft.Insert(p) | ||
|| topRight.Insert(p) | ||
|| bottomLeft.Insert(p) | ||
|| bottomRight.Insert(p); | ||
} | ||
|
||
points = null; | ||
} | ||
} | ||
} | ||
} |