Fixes up bounding boxes for octree

This commit is contained in:
2024-11-01 15:03:50 -04:00
parent abccdf06d6
commit c8b68493d4
13 changed files with 391 additions and 197 deletions

View File

@@ -17,6 +17,7 @@
#include "FileBrowser.hpp"
#include "engine/assets/model/Model.hpp"
#include "engine/debug/DEBUG_NAMES.hpp"
#include "engine/debug/drawers.hpp"
#include "engine/debug/profiling/counters.hpp"
#include "engine/debug/timing/FlameGraph.hpp"
#include "engine/descriptors/DescriptorPool.hpp"
@@ -193,11 +194,60 @@ namespace fgl::engine::gui
static GameObject* selected_object { nullptr };
void itterateGameObjectNode( FrameInfo& info, const OctTreeNode& node )
{
if ( node.isLeaf() )
{
if ( node.itemCount() == 0 ) return;
const auto& objects { node.getLeaf() };
for ( const GameObject& object : objects )
{
ImGui::PushID( object.getId() );
debug::drawLine( object.getPosition(), node.getFitCenter() );
if ( ImGui::TreeNodeEx( object.getName().c_str(), ImGuiTreeNodeFlags_Leaf ) )
{
ImGui::TreePop();
}
ImGui::PopID();
}
}
else if ( node.isBranch() )
{
const auto& branches { node.getBranches() };
for ( std::size_t x = 0; x < 2; ++x )
{
for ( std::size_t y = 0; y < 2; ++y )
{
for ( std::size_t z = 0; z < 2; ++z )
{
const auto item_count { branches[ x ][ y ][ z ]->itemCount() };
const auto str { std::format( "{}x{}x{}: {}", x, y, z, item_count ) };
if ( ImGui::TreeNodeEx(
str.c_str(),
ImGuiTreeNodeFlags_DefaultOpen | ( item_count == 0 ? ImGuiTreeNodeFlags_Leaf : 0 ) ) )
{
itterateGameObjectNode( info, *( branches[ x ][ y ][ z ] ) );
ImGui::TreePop();
}
}
}
}
}
}
void drawEntityGUI( FrameInfo& info )
{
ZoneScoped;
ImGui::Begin( OBJECT_TREE_VIEW_NAME );
itterateGameObjectNode( info, info.game_objects );
/*
for ( OctTreeNodeLeaf* leaf : info.game_objects.getAllLeafs() )
{
for ( GameObject& entity : *leaf )
@@ -212,6 +262,7 @@ namespace fgl::engine::gui
ImGui::PopID();
}
}
*/
ImGui::End();
}

View File

@@ -2,6 +2,8 @@
// Created by kj16609 on 10/28/24.
//
#include "intersections/aabb/intersections.hpp"
#include "intersections/aabc/intersections.hpp"
#include "intersections/frustum/intersections.hpp"
#include "intersections/line/intersections.hpp"

View File

@@ -0,0 +1,27 @@
//
// Created by kj16609 on 10/31/24.
//
#include "engine/primitives/points/Coordinate.hpp"
#include "engine/primitives/boxes/AxisAlignedBoundingBox.hpp"
namespace fgl::engine::intersections
{
template < CS CType >
bool contains( const AxisAlignedBoundingBox< CType >& aabb, const Coordinate< CType >& point )
{
const Coordinate< CType > centered_coordinate { point - aabb.getPosition() };
const bool top_in_range { glm::all( glm::lessThanEqual( centered_coordinate.vec(), aabb.scale() ) ) };
const bool bottom_in_range { glm::all( glm::greaterThanEqual( centered_coordinate.vec(), -aabb.scale() ) ) };
return top_in_range && bottom_in_range;
}
template bool contains< CS::World >( const AxisAlignedBoundingBox< CS::World >&, const Coordinate< CS::World >& );
} // namespace fgl::engine::intersections

View File

@@ -0,0 +1,13 @@
//
// Created by kj16609 on 10/31/24.
//
#include "engine/primitives/boxes/AxisAlignedBoundingBox.hpp"
namespace fgl::engine::intersections
{
template < CS CType >
bool contains( const AxisAlignedBoundingBox< CType >& aabb, const Coordinate< CType >& point );
} // namespace fgl::engine::intersections

View File

@@ -0,0 +1,25 @@
//
// Created by kj16609 on 10/31/24.
//
#include "engine/primitives/points/Coordinate.hpp"
#include "engine/primitives/boxes/AxisAlignedBoundingCube.hpp"
namespace fgl::engine::intersections
{
template < CS CType >
bool contains( const AxisAlignedBoundingCube< CType >& aabc, const Coordinate< CType >& point )
{
const Coordinate< CType > centered_coordinate { point - aabc.getPosition() };
const bool top_in_range { glm::all( glm::lessThanEqual( centered_coordinate.vec(), aabc.scale() ) ) };
const bool bottom_in_range { glm::all( glm::greaterThanEqual( centered_coordinate.vec(), -aabc.scale() ) ) };
return top_in_range && bottom_in_range;
}
template bool contains< CS::World >( const AxisAlignedBoundingCube< CS::World >&, const Coordinate< CS::World >& );
} // namespace fgl::engine::intersections

View File

@@ -0,0 +1,13 @@
//
// Created by kj16609 on 10/31/24.
//
#include "engine/primitives/boxes/AxisAlignedBoundingCube.hpp"
namespace fgl::engine::intersections
{
template < CS CType >
bool contains( const AxisAlignedBoundingCube< CType >& aabb, const Coordinate< CType >& point );
} // namespace fgl::engine::intersections

View File

@@ -0,0 +1,20 @@
//
// Created by kj16609 on 10/31/24.
//
#include "engine/primitives/points/Coordinate.hpp"
#include "engine/primitives/boxes/OrientedBoundingBox.hpp"
namespace fgl::engine::intersections
{
template < CS CType >
bool contains( const OrientedBoundingBox< CType >& obb, const Coordinate< CType >& point )
{
FGL_UNIMPLEMENTED();
}
template bool contains< CS::World >( const OrientedBoundingBox< CS::World >&, const Coordinate< CS::World >& );
} // namespace fgl::engine::intersections

View File

@@ -0,0 +1,13 @@
//
// Created by kj16609 on 10/31/24.
//
#include "engine/primitives/boxes/OrientedBoundingBox.hpp"
namespace fgl::engine::intersections
{
template < CS CType >
bool contains( const OrientedBoundingBox< CType >& aabb, const Coordinate< CType >& point );
} // namespace fgl::engine::intersections

View File

@@ -25,7 +25,7 @@ namespace fgl::engine
constexpr static auto SpaceType { CType };
explicit AxisAlignedBoundingBox(
AxisAlignedBoundingBox(
const Coordinate< CType > top_right_forward, const Coordinate< CType > bottom_left_back ) :
m_top_right_forward( top_right_forward ),
m_bottom_left_back( bottom_left_back )
@@ -35,7 +35,7 @@ namespace fgl::engine
assert( m_top_right_forward.z > m_bottom_left_back.z );
}
explicit AxisAlignedBoundingBox( const Coordinate< CType > midpoint, const Scale scale ) :
AxisAlignedBoundingBox( const Coordinate< CType > midpoint, const Scale scale ) :
AxisAlignedBoundingBox( midpoint + scale, midpoint - scale )
{
assert( m_top_right_forward.x > m_bottom_left_back.x );
@@ -43,6 +43,7 @@ namespace fgl::engine
assert( m_top_right_forward.z > m_bottom_left_back.z );
}
//TODO: This should be removed from access in favor of OBB::alignToWorld()
explicit AxisAlignedBoundingBox( const OrientedBoundingBox< CType >& oobb );
AxisAlignedBoundingBox& combine( const AxisAlignedBoundingBox& other );

View File

@@ -225,6 +225,12 @@ namespace fgl::engine
return AxisAlignedBoundingBox< CType >( Coordinate< CType >( max ), Coordinate< CType >( min ) );
}
template < CoordinateSpace CType >
Coordinate< CType > OrientedBoundingBox< CType >::getPosition() const
{
return m_transform.translation;
}
template < CoordinateSpace CType >
std::array< LineSegment< CType >, interface::BoundingBox::LINE_COUNT > OrientedBoundingBox< CType >::lines() const
{

View File

@@ -111,6 +111,8 @@ namespace fgl::engine
OrientedBoundingBox combine( const OrientedBoundingBox& other ) const;
AxisAlignedBoundingBox< CType > alignToWorld() const;
Coordinate< CType > getPosition() const;
};
template < CoordinateSpace CType, MatrixType MType >

View File

@@ -241,6 +241,9 @@ namespace fgl::engine
}
objects.emplace_back( std::move( obj ) );
recalculateBounds();
return this;
}
else if ( std::holds_alternative< OctTreeNodeArray >( m_node_data ) )
@@ -252,25 +255,59 @@ namespace fgl::engine
FGL_UNREACHABLE();
}
bool OctTreeNode::isLeaf() const
{
return std::holds_alternative< OctTreeNodeLeaf >( m_node_data );
}
bool OctTreeNode::isBranch() const
{
return std::holds_alternative< OctTreeNodeArray >( m_node_data );
}
std::size_t OctTreeNode::itemCount() const
{
//TODO: Store this value in the nodes itself
if ( !isLeaf() )
{
std::size_t sum { 0 };
for ( std::size_t x = 0; x < 2; ++x )
{
for ( std::size_t y = 0; y < 2; ++y )
{
for ( std::size_t z = 0; z < 2; ++z )
{
sum += std::get< OctTreeNodeArray >( m_node_data )[ x ][ y ][ z ]->itemCount();
}
}
}
return sum;
}
return std::get< OctTreeNodeLeaf >( m_node_data ).size();
}
const OctTreeNodeArray& OctTreeNode::getBranches() const
{
return std::get< OctTreeNodeArray >( m_node_data );
}
const OctTreeNodeLeaf& OctTreeNode::getLeaf() const
{
return std::get< OctTreeNodeLeaf >( m_node_data );
}
bool OctTreeNode::isInFrustum( const Frustum& frustum ) const
{
#if ENABLE_IMGUI
if ( !isEmpty() && intersects( frustum, m_fit_bounding_box ) )
{
if ( ( draw_inview_bounds || ( std::holds_alternative< OctTreeNodeLeaf >( this->m_node_data ) )
|| draw_branches )
&& m_parent ) [[unlikely]]
if ( isLeaf() && itemCount() > 0 ) [[unlikely]]
{
if ( draw_leaf_fit_bounds ) [[unlikely]]
debug::drawBoundingBox( m_fit_bounding_box );
/*
debug::drawBoundingBox(
m_fit_bounding_box,
glm::vec3(
m_fit_bounding_box.getPosition().x > 0.0f ? 1.0f : 0.0f,
m_fit_bounding_box.getPosition().y > 0.0f ? 1.0f : 0.0f,
m_fit_bounding_box.getPosition().z > 0.0f ? 1.0f : 0.0f ) );
*/
if ( draw_leaf_real_bounds ) [[unlikely]]
debug::drawBoundingBox( m_bounds );
}
@@ -285,47 +322,42 @@ namespace fgl::engine
#endif
}
bool OctTreeNode::isEmpty() const
{
return std::holds_alternative< OctTreeNodeLeaf >( m_node_data )
&& std::get< OctTreeNodeLeaf >( m_node_data ).empty();
}
/**
*
* @return Returns true if the fit bounding box is larger then the virtual bounds
*/
void OctTreeNode::recalculateBounds()
{
m_fit_bounding_box = m_bounds;
if ( isLeaf() )
return recalculateLeafBounds();
else if ( isBranch() )
return recalculateNodeBounds();
if ( std::holds_alternative< NodeDataT >( m_node_data ) )
{
// We aren't a leaf. So we need to use the bounding box of the children below us
FGL_UNREACHABLE();
}
const auto& nodes { std::get< NodeDataT >( m_node_data ) };
std::vector< OctTreeNodeLeaf* > OctTreeNode::getAllLeafs()
{
ZoneScoped;
std::vector< OctTreeNodeLeaf* > leafs {};
leafs.reserve( LEAF_RESERVE_SIZE );
this->getAllLeafs( leafs );
return leafs;
}
for ( std::size_t x = 0; x < 2; ++x )
{
for ( std::size_t y = 0; y < 2; ++y )
{
for ( std::size_t z = 0; z < 2; ++z )
{
nodes[ x ][ y ][ z ]->recalculateBounds();
m_fit_bounding_box = m_fit_bounding_box.combine( nodes[ x ][ y ][ z ]->m_fit_bounding_box );
}
}
}
}
else if ( std::holds_alternative< LeafDataT >( m_node_data ) )
{
const auto& data { std::get< LeafDataT >( m_node_data ) };
for ( const auto& game_object : data )
{
auto model_components { game_object.getComponents< ModelComponent >() };
for ( const auto& model : model_components )
{
const OrientedBoundingBox model_bounding_box { ( *model )->getBoundingBox() };
const OrientedBoundingBox< CoordinateSpace::World > world_bounding_box { model->m_transform.mat()
* model_bounding_box };
m_fit_bounding_box = m_fit_bounding_box.combine( world_bounding_box.alignToWorld() );
}
}
}
std::vector< OctTreeNodeLeaf* > OctTreeNode::getAllLeafsInFrustum( const Frustum& frustum )
{
ZoneScoped;
std::vector< OctTreeNodeLeaf* > leafs {};
leafs.reserve( LEAF_RESERVE_SIZE );
this->getAllLeafsInFrustum( frustum, leafs );
return leafs;
}
void OctTreeNode::clear()
@@ -338,12 +370,26 @@ namespace fgl::engine
{
const auto& node_array { std::get< OctTreeNodeArray >( this->m_node_data ) };
for ( std::size_t x = 0; x < 2; ++x )
for ( std::size_t y = 0; y < 2; ++y )
for ( std::size_t z = 0; z < 2; ++z ) node_array[ x ][ y ][ z ]->clear();
FOR_EACH_OCTTREE_NODE
{
node_array[ x ][ y ][ z ]->clear();
}
}
}
WorldCoordinate OctTreeNode::getCenter() const
{
return m_bounds.getPosition();
}
WorldCoordinate OctTreeNode::getFitCenter() const
{
return m_fit_bounding_box.getPosition();
}
void OctTreeNode::drawDebug() const
{}
OctTreeNode* OctTreeNode::findID( const GameObject::GameObjectID id )
{
ZoneScoped;
@@ -352,33 +398,22 @@ namespace fgl::engine
//We are the last node. Check if we have the ID
const auto& game_objects { std::get< OctTreeNodeLeaf >( m_node_data ) };
if ( std::find_if(
game_objects.begin(),
game_objects.end(),
[ id ]( const GameObject& obj ) { return obj.getId() == id; } )
if ( std::ranges::find_if( game_objects, [ id ]( const GameObject& obj ) { return obj.getId() == id; } )
!= game_objects.end() )
{
return this;
}
else
{
return nullptr;
}
return nullptr;
}
else if ( std::holds_alternative< OctTreeNodeArray >( this->m_node_data ) )
{
const auto& node_array { std::get< OctTreeNodeArray >( this->m_node_data ) };
for ( std::size_t x = 0; x < 2; ++x )
FOR_EACH_OCTTREE_NODE
{
for ( std::size_t y = 0; y < 2; ++y )
{
for ( std::size_t z = 0; z < 2; ++z )
{
const auto& node { node_array[ x ][ y ][ z ]->findID( id ) };
if ( node != nullptr ) return node;
}
}
const auto& node { node_array[ x ][ y ][ z ]->findID( id ) };
if ( node != nullptr ) return node;
}
return nullptr;
@@ -434,105 +469,14 @@ namespace fgl::engine
{
const auto& nodes { std::get< OctTreeNodeArray >( m_node_data ) };
for ( std::size_t x = 0; x < 2; ++x )
FOR_EACH_OCTTREE_NODE
{
for ( std::size_t y = 0; y < 2; ++y )
{
for ( std::size_t z = 0; z < 2; ++z )
{
auto ret { nodes[ x ][ y ][ z ]->getAllLeafs() };
out_leafs.insert( out_leafs.end(), ret.begin(), ret.end() );
}
}
auto ret { nodes[ x ][ y ][ z ]->getAllLeafs() };
out_leafs.insert( out_leafs.end(), ret.begin(), ret.end() );
}
}
}
/*
bool OctTreeNode::recalculateBoundingBoxes()
{
ZoneScoped;
const auto old_bounds { m_fit_bounding_box };
if ( std::holds_alternative< OctTreeNodeArray >( m_node_data ) )
{
ZoneScopedN( "Process Array" );
bool bounding_box_changed { false };
auto& nodes { std::get< OctTreeNodeArray >( m_node_data ) };
for ( std::size_t x = 0; x < 2; ++x )
{
for ( std::size_t y = 0; y < 2; ++y )
{
for ( std::size_t z = 0; z < 2; ++z )
{
auto& node { nodes[ x ][ y ][ z ] };
bounding_box_changed |= node->recalculateBoundingBoxes();
}
}
}
if ( bounding_box_changed )
{
//We need to update our bounding box now.
auto new_bounds { nodes[ 0 ][ 0 ][ 0 ]->m_fit_bounding_box };
for ( std::size_t x = 0; x < 2; ++x )
{
for ( std::size_t y = 0; y < 2; ++y )
{
for ( std::size_t z = 0; z < 2; ++z )
{
if ( x == 0 && y == 0 && z == 0 ) continue;
auto& node { nodes[ x ][ y ][ z ] };
new_bounds.combine( node->m_fit_bounding_box );
}
}
}
if ( new_bounds == old_bounds )
{
return false;
}
else
{
this->m_fit_bounding_box = new_bounds;
return true;
}
}
return false;
}
else if ( std::holds_alternative< OctTreeNodeLeaf >( m_node_data ) )
{
ZoneScopedN( "Process Leaf" );
auto& game_objects { std::get< OctTreeNodeLeaf >( m_node_data ) };
if ( game_objects.size() == 0 ) return false;
AxisAlignedBoundingBox< CoordinateSpace::World > new_bounds { game_objects[ 0 ].getPosition() };
FGL_ASSUME( game_objects.size() <= MAX_NODES_IN_LEAF );
for ( std::size_t i = 1; i < game_objects.size(); ++i )
{
FGL_ASSUME( i <= MAX_NODES_IN_LEAF ));
new_bounds.combine( game_objects[ i ].getBoundingBox() );
}
if ( new_bounds == old_bounds )
{
return false;
}
else
{
this->m_fit_bounding_box = new_bounds;
return true;
}
}
FGL_UNREACHABLE();
}
*/
std::size_t OctTreeNode::reorganize()
{
std::size_t counter { 0 };
@@ -540,22 +484,17 @@ namespace fgl::engine
{
const auto& nodes { std::get< NodeDataT >( m_node_data ) };
for ( std::size_t x = 0; x < 2; ++x )
FOR_EACH_OCTTREE_NODE
{
for ( std::size_t y = 0; y < 2; ++y )
{
for ( std::size_t z = 0; z < 2; ++z )
{
if ( x == 0 && y == 0 && z == 0 ) continue;
const auto& node { nodes[ x ][ y ][ z ] };
counter += node->reorganize();
}
}
// Why did I skip the 0,0,0 node previously?
// if ( x == 0 && y == 0 && z == 0 ) continue;
const auto& node { nodes[ x ][ y ][ z ] };
counter += node->reorganize();
}
return counter;
}
if ( std::holds_alternative< LeafDataT >( m_node_data ) )
else if ( std::holds_alternative< LeafDataT >( m_node_data ) )
{
//Check if any of the nodes in this group need to be moved.
@@ -578,4 +517,85 @@ namespace fgl::engine
FGL_UNREACHABLE();
}
bool OctTreeNode::isBoundsExpanded()
{
return m_fit_bounding_box == m_bounds;
/*
const auto fit_points { m_fit_bounding_box.points() };
for ( const auto& p : fit_points )
{
// Return true if a point is outside of the bounds. This indicates that out bounding box is bigger then our bounds.
if ( !m_bounds.contains( p ) ) return true;
}
return false;
*/
}
void OctTreeNode::recalculateNodeBounds()
{
FGL_ASSERT( std::holds_alternative< NodeDataT >( m_node_data ), "Node data was not an array!" );
const auto& nodes { std::get< NodeDataT >( m_node_data ) };
// We start out by telling all of our children to recalculate their bounds
m_fit_bounding_box = static_cast< AxisAlignedBoundingBox< CoordinateSpace::World > >( m_bounds );
FOR_EACH_OCTTREE_NODE
{
// If true then the bounds were bigger then the inital bounding box. So we should try to combine it with out current bounding box.
m_fit_bounding_box = m_fit_bounding_box.combine( nodes[ x ][ y ][ z ]->m_fit_bounding_box );
}
// if ( isBoundsExpanded() && m_parent )
if ( m_parent ) m_parent->recalculateBounds();
}
void OctTreeNode::recalculateLeafBounds()
{
FGL_ASSERT( std::holds_alternative< LeafDataT >( m_node_data ), "Node data was not a leaf!" );
const auto& data { std::get< LeafDataT >( m_node_data ) };
if ( data.empty() )
{
m_fit_bounding_box = static_cast< AxisAlignedBoundingBox< CoordinateSpace::World > >( m_bounds );
return;
}
// If true, Then the fit has already been set and we should combine with it
bool fit_set { false };
for ( const auto& game_object : data )
{
auto model_components { game_object.getComponents< ModelComponent >() };
const Matrix< MatrixType::ModelToWorld > game_object_transform { game_object.getTransform().mat() };
for ( const auto& model : model_components )
{
const OrientedBoundingBox< CS::Model > model_bounding_box { ( *model )->getBoundingBox() };
const Matrix< MatrixType::ModelToWorld > model_transform { model->m_transform.mat() };
// Combine the game object and model transform
const Matrix< MatrixType::ModelToWorld > combined_transform { model_transform * game_object_transform };
const OrientedBoundingBox< CoordinateSpace::World > world_bounding_box { combined_transform
* model_bounding_box };
const auto aligned_bounding_box { world_bounding_box.alignToWorld() };
if ( fit_set )
m_fit_bounding_box = m_fit_bounding_box.combine( aligned_bounding_box );
else
{
m_fit_bounding_box = aligned_bounding_box;
fit_set = true;
}
}
}
// Have our parent recalculate it's bounds
// if ( isBoundsExpanded() && m_parent )
if ( m_parent ) m_parent->recalculateBounds();
}
} // namespace fgl::engine

View File

@@ -4,8 +4,6 @@
#pragma once
#include <tracy/Tracy.hpp>
#include "engine/gameobjects/GameObject.hpp"
#include "engine/primitives/boxes/AxisAlignedBoundingCube.hpp"
@@ -65,6 +63,9 @@ namespace fgl::engine
OctTreeNode& operator=( const OctTreeNode& ) = delete;
OctTreeNode& operator=( OctTreeNode&& ) = delete;
void clear();
WorldCoordinate getCenter() const;
WorldCoordinate getFitCenter() const;
void drawDebug() const;
private:
@@ -89,11 +90,7 @@ namespace fgl::engine
bool isInFrustum( const Frustum& frustum ) const;
bool isEmpty() const
{
return std::holds_alternative< OctTreeNodeLeaf >( m_node_data )
&& std::get< OctTreeNodeLeaf >( m_node_data ).empty();
}
bool isEmpty() const;
auto getGameObjectItter( GameObject::GameObjectID id );
@@ -109,30 +106,34 @@ namespace fgl::engine
//! Rebuilds the tree checking if nodes have moved.
std::size_t reorganize();
//! Returns true if the fixed bounding box is larger then the inital bounding box
bool isBoundsExpanded();
void recalculateNodeBounds();
void recalculateLeafBounds();
void recalculateBounds();
constexpr static std::size_t LEAF_RESERVE_SIZE { 1024 };
[[nodiscard]] std::vector< OctTreeNodeLeaf* > getAllLeafs()
{
ZoneScoped;
std::vector< OctTreeNodeLeaf* > leafs {};
leafs.reserve( LEAF_RESERVE_SIZE );
this->getAllLeafs( leafs );
return leafs;
}
[[nodiscard]] std::vector< OctTreeNodeLeaf* > getAllLeafs();
[[nodiscard]] std::vector< OctTreeNodeLeaf* > getAllLeafsInFrustum( const Frustum& frustum )
{
ZoneScoped;
std::vector< OctTreeNodeLeaf* > leafs {};
leafs.reserve( LEAF_RESERVE_SIZE );
this->getAllLeafsInFrustum( frustum, leafs );
return leafs;
}
[[nodiscard]] std::vector< OctTreeNodeLeaf* > getAllLeafsInFrustum( const Frustum& frustum );
//! Adds a game object, Will split the node if the auto split threshold is reached
OctTreeNode* addGameObject( GameObject&& obj );
bool isLeaf() const;
bool isBranch() const;
std::size_t itemCount() const;
const OctTreeNodeArray& getBranches() const;
const OctTreeNodeLeaf& getLeaf() const;
};
#define FOR_EACH_OCTTREE_NODE \
for ( std::size_t x = 0; x < 2; ++x ) \
for ( std::size_t y = 0; y < 2; ++y ) \
for ( std::size_t z = 0; z < 2; ++z )
} // namespace fgl::engine