Remove entity boundaries from Quadtree
The Quadtree did no meaningful management of entity boundaries. All it did was store the maximum and inflate the query rectangle accordingly. The caller can easily take over this responsibility. Consequently, this simplifies queries.
This commit is contained in:
parent
157fea7761
commit
85fcd2358f
@ -32,11 +32,6 @@ namespace SpatialCollections
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Func<T, Vector2> _getPositionCallback;
|
private readonly Func<T, Vector2> _getPositionCallback;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Callback to determine the bounding box of an entity.
|
|
||||||
/// </summary>
|
|
||||||
private readonly Func<T, Rectangle> _getBoundingBoxCallback;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of quadtree vertices. The root vertex is always the first item. The four child vertices of a branch
|
/// The list of quadtree vertices. The root vertex is always the first item. The four child vertices of a branch
|
||||||
/// are always created together and stored contiguously.
|
/// are always created together and stored contiguously.
|
||||||
@ -50,11 +45,6 @@ namespace SpatialCollections
|
|||||||
|
|
||||||
private Rectangle _rootBoundingBox;
|
private Rectangle _rootBoundingBox;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The maximum bounding radius of all stored entities, used to inflate the bounding box for queries.
|
|
||||||
/// </summary>
|
|
||||||
private float _maxEntityBoundingRadius;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stack of vertex indices with their bounding boxes. It is reused for each query.
|
/// Stack of vertex indices with their bounding boxes. It is reused for each query.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -65,31 +55,24 @@ namespace SpatialCollections
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Stack<int> _vertexStack;
|
private readonly Stack<int> _vertexStack;
|
||||||
|
|
||||||
public Quadtree(int maxLeafSize, int maxTreeDepth, Func<T, Vector2> getPositionCallback,
|
public Quadtree(int maxLeafSize, int maxTreeDepth, Func<T, Vector2> getPositionCallback)
|
||||||
Func<T, Rectangle> getBoundingBoxCallback)
|
|
||||||
{
|
{
|
||||||
_maxLeafSize = maxLeafSize;
|
_maxLeafSize = maxLeafSize;
|
||||||
_maxTreeDepth = maxTreeDepth;
|
_maxTreeDepth = maxTreeDepth;
|
||||||
_getPositionCallback = getPositionCallback;
|
_getPositionCallback = getPositionCallback;
|
||||||
_getBoundingBoxCallback = getBoundingBoxCallback;
|
|
||||||
_vertices = new List<QuadtreeVertex>() { new(-1, 0) };
|
_vertices = new List<QuadtreeVertex>() { new(-1, 0) };
|
||||||
_items = new List<QuadtreeItem<T>>();
|
_items = new List<QuadtreeItem<T>>();
|
||||||
_rootBoundingBox = new Rectangle(Vector2.Zero, Vector2.Zero);
|
_rootBoundingBox = new Rectangle(Vector2.Zero, Vector2.Zero);
|
||||||
_maxEntityBoundingRadius = 0f;
|
|
||||||
_queryStack = new Stack<QuadtreeQuery>();
|
_queryStack = new Stack<QuadtreeQuery>();
|
||||||
_vertexStack = new Stack<int>();
|
_vertexStack = new Stack<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Count => _items.Count;
|
public int Count => _items.Count;
|
||||||
|
|
||||||
public void Add(T entity, float boundingRadius)
|
public void Add(T entity)
|
||||||
{
|
{
|
||||||
var itemIndex = _items.Count;
|
var itemIndex = _items.Count;
|
||||||
_items.Add(new QuadtreeItem<T>(entity));
|
_items.Add(new QuadtreeItem<T>(entity));
|
||||||
if (_maxEntityBoundingRadius < boundingRadius)
|
|
||||||
{
|
|
||||||
_maxEntityBoundingRadius = boundingRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_vertices.Count == 1 && _vertices[0].ChildCount < _maxLeafSize)
|
if (_vertices.Count == 1 && _vertices[0].ChildCount < _maxLeafSize)
|
||||||
{
|
{
|
||||||
@ -165,10 +148,7 @@ namespace SpatialCollections
|
|||||||
|
|
||||||
public void Query(Rectangle box, List<T> resultList)
|
public void Query(Rectangle box, List<T> resultList)
|
||||||
{
|
{
|
||||||
Rectangle inflatedBox = box;
|
QueryProcessChildVertex(box, 0, _rootBoundingBox, resultList, _queryStack);
|
||||||
inflatedBox.Expand(_maxEntityBoundingRadius);
|
|
||||||
|
|
||||||
QueryProcessChildVertex(inflatedBox, box, 0, _rootBoundingBox, resultList, _queryStack);
|
|
||||||
|
|
||||||
while (_queryStack.Count > 0)
|
while (_queryStack.Count > 0)
|
||||||
{
|
{
|
||||||
@ -177,16 +157,16 @@ namespace SpatialCollections
|
|||||||
int childIndex = _vertices[query.VertexIndex].FirstChildIndex;
|
int childIndex = _vertices[query.VertexIndex].FirstChildIndex;
|
||||||
Rectangle halfBounds = new(query.VertexBounds.Center, query.VertexBounds.Max);
|
Rectangle halfBounds = new(query.VertexBounds.Center, query.VertexBounds.Max);
|
||||||
Vector2 halfSize = halfBounds.Size;
|
Vector2 halfSize = halfBounds.Size;
|
||||||
QueryProcessChildVertex(inflatedBox, box, childIndex++, halfBounds, resultList, _queryStack);
|
QueryProcessChildVertex(box, childIndex++, halfBounds, resultList, _queryStack);
|
||||||
|
|
||||||
halfBounds.Translate(new Vector2(-halfSize.X, 0f));
|
halfBounds.Translate(new Vector2(-halfSize.X, 0f));
|
||||||
QueryProcessChildVertex(inflatedBox, box, childIndex++, halfBounds, resultList, _queryStack);
|
QueryProcessChildVertex(box, childIndex++, halfBounds, resultList, _queryStack);
|
||||||
|
|
||||||
halfBounds.Translate(new Vector2(halfSize.X, -halfSize.Y));
|
halfBounds.Translate(new Vector2(halfSize.X, -halfSize.Y));
|
||||||
QueryProcessChildVertex(inflatedBox, box, childIndex++, halfBounds, resultList, _queryStack);
|
QueryProcessChildVertex(box, childIndex++, halfBounds, resultList, _queryStack);
|
||||||
|
|
||||||
halfBounds.Translate(new Vector2(-halfSize.X, 0f));
|
halfBounds.Translate(new Vector2(-halfSize.X, 0f));
|
||||||
QueryProcessChildVertex(inflatedBox, box, childIndex, halfBounds, resultList, _queryStack);
|
QueryProcessChildVertex(box, childIndex, halfBounds, resultList, _queryStack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,7 +176,6 @@ namespace SpatialCollections
|
|||||||
_vertices.Add(new QuadtreeVertex(-1, 0));
|
_vertices.Add(new QuadtreeVertex(-1, 0));
|
||||||
_items.Clear();
|
_items.Clear();
|
||||||
_rootBoundingBox = new Rectangle(Vector2.Zero, Vector2.Zero);
|
_rootBoundingBox = new Rectangle(Vector2.Zero, Vector2.Zero);
|
||||||
_maxEntityBoundingRadius = 0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -342,21 +321,21 @@ namespace SpatialCollections
|
|||||||
return (vertexIndex, depth, bounds);
|
return (vertexIndex, depth, bounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void QueryProcessChildVertex(Rectangle inflatedBox, Rectangle box, int vertexIndex,
|
private void QueryProcessChildVertex(Rectangle box, int vertexIndex, Rectangle vertexBounds,
|
||||||
Rectangle vertexBounds, List<T> resultList, Stack<QuadtreeQuery> stack)
|
List<T> resultList, Stack<QuadtreeQuery> stack)
|
||||||
{
|
{
|
||||||
switch (inflatedBox.Intersects(vertexBounds))
|
switch (box.Intersects(vertexBounds))
|
||||||
{
|
{
|
||||||
case IntersectionType.Contains:
|
case IntersectionType.Contains:
|
||||||
if (_vertices[vertexIndex].ChildCount == -1)
|
if (_vertices[vertexIndex].ChildCount == -1)
|
||||||
{
|
{
|
||||||
// Found contained vertex, appends items of all child vertices.
|
// Found contained vertex, appends items of all child vertices.
|
||||||
QueryAppendContainedItems(vertexIndex, box, resultList);
|
QueryAppendItems(vertexIndex, resultList);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Found leaf, appends items.
|
// Found leaf, appends items.
|
||||||
QueryAppendContainedLeafItems(vertexIndex, box, resultList);
|
QueryAppendLeafItems(vertexIndex, resultList);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case IntersectionType.Intersects:
|
case IntersectionType.Intersects:
|
||||||
@ -367,14 +346,14 @@ namespace SpatialCollections
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Found leaf, appends items.
|
// Found leaf, appends contained items.
|
||||||
QueryAppendContainedLeafItems(vertexIndex, box, resultList);
|
QueryAppendContainedLeafItems(vertexIndex, box, resultList);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void QueryAppendContainedItems(int vertexIndex, Rectangle box, List<T> resultList)
|
private void QueryAppendItems(int vertexIndex, List<T> resultList)
|
||||||
{
|
{
|
||||||
_vertexStack.Push(vertexIndex);
|
_vertexStack.Push(vertexIndex);
|
||||||
|
|
||||||
@ -393,22 +372,34 @@ namespace SpatialCollections
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Found leaf, appends items.
|
// Found leaf, appends items.
|
||||||
QueryAppendContainedLeafItems(current, box, resultList);
|
QueryAppendLeafItems(current, resultList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void QueryAppendLeafItems(int vertexIndex, List<T> resultList)
|
||||||
|
{
|
||||||
|
int index = _vertices[vertexIndex].FirstChildIndex;
|
||||||
|
while (index != -1)
|
||||||
|
{
|
||||||
|
var item = _items[index];
|
||||||
|
resultList.Add(item.Entity);
|
||||||
|
index = item.Next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void QueryAppendContainedLeafItems(int vertexIndex, Rectangle box, List<T> resultList)
|
private void QueryAppendContainedLeafItems(int vertexIndex, Rectangle box, List<T> resultList)
|
||||||
{
|
{
|
||||||
int index = _vertices[vertexIndex].FirstChildIndex;
|
int index = _vertices[vertexIndex].FirstChildIndex;
|
||||||
while (index != -1)
|
while (index != -1)
|
||||||
{
|
{
|
||||||
T entity = _items[index].Entity;
|
var item = _items[index];
|
||||||
if (box.Intersects(_getBoundingBoxCallback(entity)) != IntersectionType.Disjoint)
|
T entity = item.Entity;
|
||||||
|
if (box.Contains(_getPositionCallback(entity)))
|
||||||
{
|
{
|
||||||
resultList.Add(entity);
|
resultList.Add(entity);
|
||||||
}
|
}
|
||||||
index = _items[index].Next;
|
index = item.Next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
using NSubstitute;
|
|
||||||
using SpatialCollections;
|
using SpatialCollections;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
@ -6,13 +5,9 @@ namespace QuadtreeTests
|
|||||||
{
|
{
|
||||||
public class Tests
|
public class Tests
|
||||||
{
|
{
|
||||||
private const float BoundingRadius = 1.0f;
|
|
||||||
|
|
||||||
private class TestEntity(Vector2 position)
|
private class TestEntity(Vector2 position)
|
||||||
{
|
{
|
||||||
public Vector2 Position { get; } = position;
|
public Vector2 Position { get; } = position;
|
||||||
|
|
||||||
public Rectangle BoundingBox { get; } = new Rectangle(position - Vector2.One, position + Vector2.One);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Vector2> _positions;
|
private List<Vector2> _positions;
|
||||||
@ -26,7 +21,7 @@ namespace QuadtreeTests
|
|||||||
{
|
{
|
||||||
_positions = [new Vector2(1.0f, 1.0f), new Vector2(3.0f, 3.0f), new Vector2(5.0f, 8.0f)];
|
_positions = [new Vector2(1.0f, 1.0f), new Vector2(3.0f, 3.0f), new Vector2(5.0f, 8.0f)];
|
||||||
_entities = new();
|
_entities = new();
|
||||||
_quadtree = new(20, 4, e => e.Position, e => e.BoundingBox);
|
_quadtree = new(20, 4, e => e.Position);
|
||||||
|
|
||||||
for (int i = 0; i < _positions.Count; i++)
|
for (int i = 0; i < _positions.Count; i++)
|
||||||
{
|
{
|
||||||
@ -58,7 +53,7 @@ namespace QuadtreeTests
|
|||||||
{
|
{
|
||||||
AddObjectsAndAssertCount();
|
AddObjectsAndAssertCount();
|
||||||
List<TestEntity> result = new();
|
List<TestEntity> result = new();
|
||||||
_quadtree.Query(new Rectangle(new Vector2(3.5f, 3.5f), new Vector2(10.0f, 10.0f)), result);
|
_quadtree.Query(new Rectangle(new Vector2(2.5f, 2.5f), new Vector2(10.0f, 10.0f)), result);
|
||||||
Assert.That(result.Count, Is.EqualTo(2));
|
Assert.That(result.Count, Is.EqualTo(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +61,7 @@ namespace QuadtreeTests
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < _entities.Count; i++)
|
for (int i = 0; i < _entities.Count; i++)
|
||||||
{
|
{
|
||||||
_quadtree.Add(_entities[i], BoundingRadius);
|
_quadtree.Add(_entities[i]);
|
||||||
Assert.That(_quadtree.Count, Is.EqualTo(i + 1));
|
Assert.That(_quadtree.Count, Is.EqualTo(i + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user