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>
|
||||
private readonly Func<T, Vector2> _getPositionCallback;
|
||||
|
||||
/// <summary>
|
||||
/// Callback to determine the bounding box of an entity.
|
||||
/// </summary>
|
||||
private readonly Func<T, Rectangle> _getBoundingBoxCallback;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
@ -50,11 +45,6 @@ namespace SpatialCollections
|
||||
|
||||
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>
|
||||
/// Stack of vertex indices with their bounding boxes. It is reused for each query.
|
||||
/// </summary>
|
||||
@ -65,31 +55,24 @@ namespace SpatialCollections
|
||||
/// </summary>
|
||||
private readonly Stack<int> _vertexStack;
|
||||
|
||||
public Quadtree(int maxLeafSize, int maxTreeDepth, Func<T, Vector2> getPositionCallback,
|
||||
Func<T, Rectangle> getBoundingBoxCallback)
|
||||
public Quadtree(int maxLeafSize, int maxTreeDepth, Func<T, Vector2> getPositionCallback)
|
||||
{
|
||||
_maxLeafSize = maxLeafSize;
|
||||
_maxTreeDepth = maxTreeDepth;
|
||||
_getPositionCallback = getPositionCallback;
|
||||
_getBoundingBoxCallback = getBoundingBoxCallback;
|
||||
_vertices = new List<QuadtreeVertex>() { new(-1, 0) };
|
||||
_items = new List<QuadtreeItem<T>>();
|
||||
_rootBoundingBox = new Rectangle(Vector2.Zero, Vector2.Zero);
|
||||
_maxEntityBoundingRadius = 0f;
|
||||
_queryStack = new Stack<QuadtreeQuery>();
|
||||
_vertexStack = new Stack<int>();
|
||||
}
|
||||
|
||||
public int Count => _items.Count;
|
||||
|
||||
public void Add(T entity, float boundingRadius)
|
||||
public void Add(T entity)
|
||||
{
|
||||
var itemIndex = _items.Count;
|
||||
_items.Add(new QuadtreeItem<T>(entity));
|
||||
if (_maxEntityBoundingRadius < boundingRadius)
|
||||
{
|
||||
_maxEntityBoundingRadius = boundingRadius;
|
||||
}
|
||||
|
||||
if (_vertices.Count == 1 && _vertices[0].ChildCount < _maxLeafSize)
|
||||
{
|
||||
@ -165,10 +148,7 @@ namespace SpatialCollections
|
||||
|
||||
public void Query(Rectangle box, List<T> resultList)
|
||||
{
|
||||
Rectangle inflatedBox = box;
|
||||
inflatedBox.Expand(_maxEntityBoundingRadius);
|
||||
|
||||
QueryProcessChildVertex(inflatedBox, box, 0, _rootBoundingBox, resultList, _queryStack);
|
||||
QueryProcessChildVertex(box, 0, _rootBoundingBox, resultList, _queryStack);
|
||||
|
||||
while (_queryStack.Count > 0)
|
||||
{
|
||||
@ -177,16 +157,16 @@ namespace SpatialCollections
|
||||
int childIndex = _vertices[query.VertexIndex].FirstChildIndex;
|
||||
Rectangle halfBounds = new(query.VertexBounds.Center, query.VertexBounds.Max);
|
||||
Vector2 halfSize = halfBounds.Size;
|
||||
QueryProcessChildVertex(inflatedBox, box, childIndex++, halfBounds, resultList, _queryStack);
|
||||
QueryProcessChildVertex(box, childIndex++, halfBounds, resultList, _queryStack);
|
||||
|
||||
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));
|
||||
QueryProcessChildVertex(inflatedBox, box, childIndex++, halfBounds, resultList, _queryStack);
|
||||
QueryProcessChildVertex(box, childIndex++, halfBounds, resultList, _queryStack);
|
||||
|
||||
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));
|
||||
_items.Clear();
|
||||
_rootBoundingBox = new Rectangle(Vector2.Zero, Vector2.Zero);
|
||||
_maxEntityBoundingRadius = 0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -342,21 +321,21 @@ namespace SpatialCollections
|
||||
return (vertexIndex, depth, bounds);
|
||||
}
|
||||
|
||||
private void QueryProcessChildVertex(Rectangle inflatedBox, Rectangle box, int vertexIndex,
|
||||
Rectangle vertexBounds, List<T> resultList, Stack<QuadtreeQuery> stack)
|
||||
private void QueryProcessChildVertex(Rectangle box, int vertexIndex, Rectangle vertexBounds,
|
||||
List<T> resultList, Stack<QuadtreeQuery> stack)
|
||||
{
|
||||
switch (inflatedBox.Intersects(vertexBounds))
|
||||
switch (box.Intersects(vertexBounds))
|
||||
{
|
||||
case IntersectionType.Contains:
|
||||
if (_vertices[vertexIndex].ChildCount == -1)
|
||||
{
|
||||
// Found contained vertex, appends items of all child vertices.
|
||||
QueryAppendContainedItems(vertexIndex, box, resultList);
|
||||
QueryAppendItems(vertexIndex, resultList);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Found leaf, appends items.
|
||||
QueryAppendContainedLeafItems(vertexIndex, box, resultList);
|
||||
QueryAppendLeafItems(vertexIndex, resultList);
|
||||
}
|
||||
break;
|
||||
case IntersectionType.Intersects:
|
||||
@ -367,14 +346,14 @@ namespace SpatialCollections
|
||||
}
|
||||
else
|
||||
{
|
||||
// Found leaf, appends items.
|
||||
// Found leaf, appends contained items.
|
||||
QueryAppendContainedLeafItems(vertexIndex, box, resultList);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void QueryAppendContainedItems(int vertexIndex, Rectangle box, List<T> resultList)
|
||||
private void QueryAppendItems(int vertexIndex, List<T> resultList)
|
||||
{
|
||||
_vertexStack.Push(vertexIndex);
|
||||
|
||||
@ -393,22 +372,34 @@ namespace SpatialCollections
|
||||
else
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
int index = _vertices[vertexIndex].FirstChildIndex;
|
||||
while (index != -1)
|
||||
{
|
||||
T entity = _items[index].Entity;
|
||||
if (box.Intersects(_getBoundingBoxCallback(entity)) != IntersectionType.Disjoint)
|
||||
var item = _items[index];
|
||||
T entity = item.Entity;
|
||||
if (box.Contains(_getPositionCallback(entity)))
|
||||
{
|
||||
resultList.Add(entity);
|
||||
}
|
||||
index = _items[index].Next;
|
||||
index = item.Next;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
using NSubstitute;
|
||||
using SpatialCollections;
|
||||
using System.Numerics;
|
||||
|
||||
@ -6,13 +5,9 @@ namespace QuadtreeTests
|
||||
{
|
||||
public class Tests
|
||||
{
|
||||
private const float BoundingRadius = 1.0f;
|
||||
|
||||
private class TestEntity(Vector2 position)
|
||||
{
|
||||
public Vector2 Position { get; } = position;
|
||||
|
||||
public Rectangle BoundingBox { get; } = new Rectangle(position - Vector2.One, position + Vector2.One);
|
||||
}
|
||||
|
||||
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)];
|
||||
_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++)
|
||||
{
|
||||
@ -58,7 +53,7 @@ namespace QuadtreeTests
|
||||
{
|
||||
AddObjectsAndAssertCount();
|
||||
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));
|
||||
}
|
||||
|
||||
@ -66,7 +61,7 @@ namespace QuadtreeTests
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user