Spatial Tree
TSpatialTree is a template for creating spatial partitioning data structures - quad-trees for 2D space and oct-trees for 3D space. These structures efficiently organize spatial data to enable fast queries for elements within specific regions or overlapping certain bounds.
Creating a spatial tree
Defining options
Before creating a tree, define an options class that specifies tree behavior:
struct MyTreeOptions
{
enum
{
LoosePadding = 4,
MinimumElementsPerNode = 8,
MaximumElementsPerNode = 16,
MaximumDepth = 8
};
static simd::AABox GetBounds(const GameObject& element, void* context)
{
return element.GetWorldBounds();
}
static void SetElementId(const GameObject& element, const SpatialTreeElementId& elementId, void* context)
{
element.SetSpatialTreeId(elementId);
}
};
The options class must provide:
-
LoosePadding - Denominator for calculating node padding. Padding is (1.0f / LoosePadding). Higher values reduce padding but may cause elements to get stuck on parent nodes when straddling boundaries.
-
MinimumElementsPerNode - When element count drops below this on removal, the node collapses and moves elements back to the parent.
-
MaximumElementsPerNode - When element count exceeds this, the node subdivides into children (unless at maximum depth).
-
MaximumDepth - Maximum tree depth. Nodes at this depth will not subdivide regardless of element count.
-
GetBounds(element, context) - Returns SIMD bounds for an element.
-
SetElementId(element, elementId, context) - Called when an element's ID is assigned or changed.
Creating an octree (3D)
Use TOctTree for 3D spatial partitioning:
// Create octree centered at origin with 1000 unit extent
Vector3 center(0.0f, 0.0f, 0.0f);
float extent = 1000.0f;
TOctTree<GameObject, MyTreeOptions> tree(center, extent);
Creating a quadtree (2D)
Use TQuadTree for 2D spatial partitioning:
// Create quadtree centered at origin with 500 unit extent
Vector2 center(0.0f, 0.0f);
float extent = 500.0f;
TQuadTree<Entity2D, MyTree2DOptions> tree(center, extent);
Adding and removing elements
Adding elements
Use TSpatialTree::AddElement to insert elements:
TOctTree<GameObject, MyTreeOptions> tree(Vector3::kZero, 1000.0f);
GameObject player;
player.SetPosition(Vector3(10.0f, 5.0f, 0.0f));
tree.AddElement(player);
The tree automatically determines the appropriate node for the element based on its bounds.
Removing elements
Use TSpatialTree::RemoveElement with the element's SpatialTreeElementId:
SpatialTreeElementId elementId = player.GetSpatialTreeId();
tree.RemoveElement(elementId);
The SpatialTreeElementId is provided to your element via the SetElementId() method in your options class.
Querying the tree
Iterating all elements
Use TSpatialTree::TreeIterator to iterate over every element:
TOctTree<GameObject, MyTreeOptions>::TreeIterator iterator(tree);
while(iterator.MoveNext())
{
const GameObject& element = iterator.GetElement();
B3D_LOG(Info, LogGeneric, "Element at: {0}", element.GetPosition());
}
Querying by area intersection
Use TSpatialTree::AreaIntersectIterator to find elements intersecting specific bounds:
// Find all objects within a 50-unit box around the player
AABox queryBounds(playerPosition, Vector3(50.0f, 50.0f, 50.0f));
TOctTree<GameObject, MyTreeOptions>::AreaIntersectIterator iterator(tree, queryBounds);
while(iterator.MoveNext())
{
const GameObject& element = iterator.GetElement();
ProcessNearbyObject(element);
}
Advanced traversal
Manual node iteration
Use TSpatialTree::NodeIterator for custom tree traversal:
TOctTree<GameObject, MyTreeOptions>::NodeIterator nodeIterator(tree);
while(nodeIterator.MoveNext())
{
const auto& nodeContext = nodeIterator.GetCurrent();
const auto* node = nodeContext.Node;
// Process elements in this node
TOctTree<GameObject, MyTreeOptions>::ElementIterator elementIterator(node);
while(elementIterator.MoveNext())
{
const GameObject& element = elementIterator.GetElement();
ProcessElement(element);
}
// Optionally traverse child nodes
auto childRange = node->GetChildren();
for(u32 i = 0; i < 8; i++) // 8 children for octree
{
if(childRange.Contains(i) && node->HasChild(i))
{
nodeIterator.PushChild(i);
}
}
}
Conditional traversal
AABox frustumBounds = CalculateFrustumBounds(camera);
TOctTree<GameObject, MyTreeOptions>::NodeIterator nodeIterator(tree);
while(nodeIterator.MoveNext())
{
const auto& nodeContext = nodeIterator.GetCurrent();
const AABox& nodeBounds = nodeContext.Bounds.GetBounds();
// Skip nodes completely outside the frustum
if(!frustumBounds.Overlaps(nodeBounds))
continue;
// Process elements in visible nodes
TOctTree<GameObject, MyTreeOptions>::ElementIterator elementIterator(nodeContext.Node);
while(elementIterator.MoveNext())
{
const GameObject& element = elementIterator.GetElement();
RenderElement(element);
}
// Add intersecting child nodes for traversal
auto childRange = nodeContext.Bounds.FindIntersectingChildren(simd::AABox(frustumBounds));
for(u32 i = 0; i < 8; i++)
{
if(childRange.Contains(i) && nodeContext.Node->HasChild(i))
{
nodeIterator.PushChild(i);
}
}
}