A technical article for programmers, with code and introductory documentation for a generalised octree data structure in C++. It allows storage of any object type, and application of various algorithms. (version 2) [800 words] [1000 code lines]
Octree, Data structure, C++, Design, Implementation, Programming
The purpose was to make a reusable octree component in C++. First, that means holding objects of any type, and second, allowing manipulation by any algorithm. As supplementary requirements, it ought to be compact in active memory use, and code size, and be simple to use.
The result: It allows any object type, whilst maintaining type-safety. It allows a range of algorithmic applications, without handing over complete access. It is minimal in storage. It is moderately simple to use. It is purely an index, leaving storage management of the items to external means.
The represented octree is cubical and axis aligned, partitions are axis aligned, partitions divide in half, each level partitions the previous level in all three axises.
Algorithms implemented using the Octree include: selecting photons within a spherical shell region, and finding nearest object intersection.
The principal design structure is a division into two halves: octree and agent/visitor. The octree is used as-is, the agent and visitor are overridden by a client-written subclasses. The secondary structure, perpendicular to the primary, is a layering into interface and implementation. The interface presents the three main classes as thin templates. The implementation handles containment and item access polymorphically.
An octree requires its contained items to provide positional info. But requiring the item classes to implement an OctreeItem
interface would impose a direct interface change on every prospective item type, and enlarge their instances with a vptr. Making the octree a simple template would expand the code size considerably.
Instead, the octree related interface/implementation of the item is transferred away from the item type into the separate agent class. The octree can now hold void
pointers to items and call the agent to supply information about them. This is used in insertion and removal commands.
Also using this indirect virtual pattern is the visitor. This is like the conventional Visitor: defining an externalised operation on the main data structure. In this case however, the octree is constant and the visitor is the object modified. This provides callbacks to read tree nodes for visit queries.
The octree template wrapper ensures the items indexed by the octree and the agents and visitors used when accessing them are of matching types. All work is delegated to its Composite pattern implementation, which works with abstract base interfaces and void
pointers.
Attention to the numerical nature of the data structure is needed: OO thinking might first suggest a cubelet class knowing its boundary. But separate cubelets share boundary faces, and this must be represented carefully numerically, otherwise gaps will appear between them. This leads to modelling divisions of eight cubelets, rather than individuals. Ultimately the dimensions were ‘factored’ into their minimal form: the size of the root. They are then passed through and divided at each level, in traversal. A further benefit is that this needs no storage in the tree.
During a visit, execution jumps back and forth between client and framework. First the client calls the octree to start. The octree calls back to the visitor, which can then call the framework again to continue further. This is repeated at each tree level, allowing the client visitor to collect state and steer the traversal.
The client interface is in one hpp-cpp
file pair containing three class templates: Octree
, OctreeAgent
and OctreeVisitor
. The implementation is in two hpp-cpp
file pairs. One contains the classes for the Composite (relating to structure). The other contains the rest of the supporting classes (relating to manipulation).
Each class definition is in sections. The basic ones are: standard object services (constructors, copying...), commands, queries, and fields. Any inlines and method templates immediately follow the class definition. Three other modules are used, but not shown: Primitives
, Array
, Vector3f
. Primitives
merely collects a few aliases and limits for built-in types. Array
is a simpler, compacter alternative to std::vector
. Vector3f
is an ordinary 3D vector.
The core component is 939 lines of code. The total, including the support classes, is 1742 lines.
Some of the support classes depend on the standard C library through float.h
and math.c
, in a probably easily removable way. Exception handling is used for dealing with storage allocation exceptions. RTTI is not used. Only basic use is made of templates: just class templates. The component and support has been compiled with MinGW 3.1.0 GCC 3.4.2, and VC toolkit 2003.
Everything is available in normal text file form in this archive: http://www.hxa7241.org/articles/content/octree-general-cpp_hxa7241_2005.zip
#ifndef Octree_h
#define Octree_h
#include "OctreeAuxiliary.hpp"
#include "OctreeImplementation.hpp"
#include "hxa7241_graphics.hpp"
namespace hxa7241_graphics
{
using hxa7241_graphics::Vector3f;
/**
* agent abstract base, for client use with Octree.<br/><br/>
*
* client of Octree must define a concrete derivative of
* OctreeAgent<ItemType>.<br/><br/>
*
* this is similar to a proxy: its an intermediary for an octree to query
* its typeless subject items, when inserting or removing.<br/><br/>
*
* the overlap methods are to determine an items relation to a cell or cells,
* for insertion or removal. the parameters supply the bounds of the cell.
* <br/><br/>
*
* return value of getSubcellOverlaps is 8 bits, each bit is a bool
* corresponding to a subcell, the high bit for subcell 7, the low bit for
* subcell 0.<br/><br/>
*
* subcell numbering:
* <pre>
* y z 6 7
* |/ 2 3 4 5
* -x 0 1
* </pre>
* in binary:
* <pre>
* y z 110 111
* |/ 010 011 100 101
* -x 000 001
* </pre>
*
* @implementation
* the ___V methods simply apply a type cast to void*s and forward to their
* abstract counterparts.<br/><br/>
*
* an octree requires its contained items to provide positional info. but
* requiring the item classes to implement an OctreeItem interface would
* impose a direct interface change on every prospective item type, and enlarge
* their instances with a vptr.<br/><br/>
*
* instead, this agent transfers the octree related interface/implementation
* away from the item type into a separate class. the octree can now hold void
* pointers to items and call the agent to query them indirectly.<br/><br/>
*/
template<class TYPE>
class OctreeAgent
: public OctreeAgentV
{
/// standard object services ---------------------------------------------------
protected:
OctreeAgent() {}
public:
virtual ~OctreeAgent() {}
private:
OctreeAgent( const OctreeAgent& );
OctreeAgent& operator=( const OctreeAgent& );
/// void-to-type forwarders
public:
/// queries --------------------------------------------------------------------
virtual bool isOverlappingCellV ( const void* pItem,
const Vector3f& lowerCorner,
const Vector3f& upperCorner ) const;
virtual dword getSubcellOverlapsV( const void* pItem,
const Vector3f& lower,
const Vector3f& middle,
const Vector3f& upper ) const;
/// abstract interface
protected:
/// queries --------------------------------------------------------------------
virtual bool isOverlappingCell ( const TYPE& item,
const Vector3f& lowerCorner,
const Vector3f& upperCorner ) const =0;
virtual dword getSubcellOverlaps( const TYPE& item,
const Vector3f& lower,
const Vector3f& middle,
const Vector3f& upper ) const =0;
};
/// void-to-type forwarders
template<class TYPE>
inline
bool OctreeAgent<TYPE>::isOverlappingCellV
(
const void* pItem,
const Vector3f& lowerCorner,
const Vector3f& upperCorner
) const
{
bool is = false;
if( pItem != 0 )
{
is = isOverlappingCell( *reinterpret_cast<const TYPE*>( pItem ),
lowerCorner, upperCorner );
}
return is;
}
template<class TYPE>
inline
dword OctreeAgent<TYPE>::getSubcellOverlapsV
(
const void* pItem,
const Vector3f& lower,
const Vector3f& middle,
const Vector3f& upper
) const
{
dword ov = ALL_OUTSIDE;
if( pItem != 0 )
{
ov = getSubcellOverlaps( *reinterpret_cast<const TYPE*>( pItem ),
lower, middle, upper );
}
return ov;
}
/**
* visitor abstract base, for client use with Octree.<br/><br/>
*
* client of Octree must define a concrete derivative of
* OctreeVisitor<ItemType>.<br/><br/>
*
* this is a reversal of the Visitor pattern: it allows an operation to be
* performed with the octree, except the octree is merely read from and it is
* the visitor that is modified.<br/><br/>
*
* the visit methods are called by the tree nodes during the visit operation.
* the parameters supply the cell and boundary info. the implementation can
* call visit on the supplied cell.<br/><br/>
*
* the implementation of visitBranch needs to make the OctreeData to be given
* in each call of visit.
*
* subcell numbering:
* <pre>
* y z 6 7
* |/ 2 3 4 5
* -x 0 1
* </pre>
* in binary:
* <pre>
* y z 110 111
* |/ 010 011 100 101
* -x 000 001
* </pre>
*
* @implementation
* the ___V methods simply apply a type cast to void*s and forward to their
* abstract counterparts.<br/><br/>
*/
template<class TYPE>
class OctreeVisitor
: public OctreeVisitorV
{
/// standard object services ---------------------------------------------------
protected:
OctreeVisitor() {}
public:
virtual ~OctreeVisitor() {}
private:
OctreeVisitor( const OctreeVisitor& );
OctreeVisitor& operator=( const OctreeVisitor& );
/// void-to-type forwarders
public:
/// commands -------------------------------------------------------------------
virtual void visitRootV ( const OctreeCell* pRootCell,
const OctreeData& octreeData );
virtual void visitBranchV( const OctreeCell* subCells[8],
const OctreeData& octreeData );
virtual void visitLeafV ( const Array<const void*>& items,
const OctreeData& octreeData );
/// abstract interface
protected:
/// commands -------------------------------------------------------------------
virtual void visitRoot ( const OctreeCell* pRootCell,
const OctreeData& octreeData ) =0;
virtual void visitBranch( const OctreeCell* subCells[8],
const OctreeData& octreeData ) =0;
virtual void visitLeaf ( const Array<const TYPE*>& items,
const OctreeData& octreeData ) =0;
};
/// void-to-type forwarders
template<class TYPE>
inline
void OctreeVisitor<TYPE>::visitRootV
(
const OctreeCell* pRootCell,
const OctreeData& octreeData
)
{
visitRoot( pRootCell, octreeData );
}
template<class TYPE>
inline
void OctreeVisitor<TYPE>::visitBranchV
(
const OctreeCell* subCells[8],
const OctreeData& octreeData
)
{
visitBranch( subCells, octreeData );
}
template<class TYPE>
inline
void OctreeVisitor<TYPE>::visitLeafV
(
const Array<const void*>& items,
const OctreeData& octreeData
)
{
visitLeaf( reinterpret_cast<const Array<const TYPE*>&>( items ),
octreeData );
}
/**
* octree based spatial index.<br/><br/>
*
* client must define concrete derivatives of OctreeAgent<ItemType> and
* OctreeVisitor<ItemType>.<br/><br/>
*
* maxItemCountPerCell is ignored where maxLevelCount is reached.<br/><br/>
*
* the octree is cubical and axis aligned, partitions are axis aligned,
* partitions divide in half, each level partitions the previous level in all
* three axiss.<br/><br/>
*
* storage is contracted or expanded as needed by item insertion and removal.
* <br/><br/>
*
* (occupies, very approximately, 20 bytes per point item. maybe...)
*
* octree is only an index: it points to client items, it does not manage
* storage of items themselves.<br/><br/>
*
* @see OctreeAgent
* @see OctreeVisitor
*
* @implementation
* the octree structure follows the Composite pattern.<br/><br/>
*
* this template wrapper ensures the items indexed by the octree and the agents
* and visitors used when accessing them are of matching types. all algorithmic
* work is delegated to OctreeRoot and OctreeCell derivatives in
* OctreeImplementation, which work with abstract base interfaces and void
* pointers.<br/><br/>
*
* for the insertion and removal commands, the agent provides an interface for
* the octree to query the typeless item, and for the visit query, the visitor
* provides callbacks to read tree nodes for carrying out the visit operation.
*/
template<class TYPE>
class Octree
{
/// standard object services ---------------------------------------------------
public:
Octree( const Vector3f& positionOfLowerCorner,
float sizeOfCube,
dword maxItemCountPerCell,
dword maxLevelCount );
virtual ~Octree();
Octree( const Octree& );
Octree& operator=( const Octree& );
/// commands -------------------------------------------------------------------
virtual bool insertItem( const TYPE& item,
const OctreeAgent<TYPE>& agent );
virtual bool removeItem( const TYPE& item,
const OctreeAgent<TYPE>& agent );
/// queries --------------------------------------------------------------------
virtual void visit( OctreeVisitor<TYPE>& visitor ) const;
virtual bool isEmpty() const;
virtual void getInfo( dword& byteSize,
dword& leafCount,
dword& itemRefCount,
dword& maxDepth ) const;
virtual const Vector3f& getPosition() const;
virtual float getSize() const;
virtual dword getMaxItemCountPerCell() const;
virtual dword getMaxLevelCount() const;
/// fields ---------------------------------------------------------------------
private:
OctreeRoot root_m;
};
/// templates ///
/// standard object services ---------------------------------------------------
template<class TYPE>
inline
Octree<TYPE>::Octree
(
const Vector3f& position,
const float sizeOfCube,
const dword maxItemCountPerCell,
const dword maxLevelCount
)
: root_m( position, sizeOfCube, maxItemCountPerCell, maxLevelCount )
{
}
template<class TYPE>
inline
Octree<TYPE>::~Octree()
{
}
template<class TYPE>
inline
Octree<TYPE>::Octree
(
const Octree& other
)
: root_m( other.root_m )
{
}
template<class TYPE>
inline
Octree<TYPE>& Octree<TYPE>::operator=
(
const Octree& other
)
{
root_m = other.root_m;
return *this;
}
/// commands -------------------------------------------------------------------
template<class TYPE>
inline
bool Octree<TYPE>::insertItem
(
const TYPE& item,
const OctreeAgent<TYPE>& agent
)
{
return root_m.insertItem( &item, agent );
}
template<class TYPE>
inline
bool Octree<TYPE>::removeItem
(
const TYPE& item,
const OctreeAgent<TYPE>& agent
)
{
return root_m.removeItem( &item, agent );
}
/// queries --------------------------------------------------------------------
template<class TYPE>
inline
void Octree<TYPE>::visit
(
OctreeVisitor<TYPE>& visitor
) const
{
root_m.visit( visitor );
}
template<class TYPE>
inline
bool Octree<TYPE>::isEmpty() const
{
return root_m.isEmpty();
}
template<class TYPE>
inline
void Octree<TYPE>::getInfo
(
dword& byteSize,
dword& leafCount,
dword& itemRefCount,
dword& maxDepth
) const
{
root_m.getInfo( sizeof(*this), byteSize, leafCount,
itemRefCount, maxDepth );
}
template<class TYPE>
inline
const Vector3f& Octree<TYPE>::getPosition() const
{
return root_m.getPosition();
}
template<class TYPE>
inline
float Octree<TYPE>::getSize() const
{
return root_m.getSize();
}
template<class TYPE>
inline
dword Octree<TYPE>::getMaxItemCountPerCell() const
{
return root_m.getMaxItemCountPerCell();
}
template<class TYPE>
inline
dword Octree<TYPE>::getMaxLevelCount() const
{
return root_m.getMaxLevelCount();
}
}//namespace
#endif//Octree_h
#ifndef OctreeAuxiliary_h
#define OctreeAuxiliary_h
#include "Vector3f.hpp"
#include "Array.hpp"
#include "hxa7241_graphics.hpp"
namespace hxa7241_graphics
{
using hxa7241_graphics::Vector3f;
using hxa7241_general::Array;
class OctreeRoot;
class OctreeCell;
/**
* global octree data.<br/><br/>
*
* constant.
*/
class OctreeDimensions
{
/// standard object services ---------------------------------------------------
public:
OctreeDimensions( const Vector3f& positionOfLowerCorner,
float size,
dword maxItemCountPerCell,
dword maxLevelCount );
~OctreeDimensions();
OctreeDimensions( const OctreeDimensions& );
OctreeDimensions& operator=( const OctreeDimensions& );
/// queries --------------------------------------------------------------------
const Vector3f& getPosition() const;
float getSize() const;
dword getMaxItemCountPerCell() const;
dword getMaxLevelCount() const;
bool isSubdivide( dword itemCount,
dword level ) const;
/// fields ---------------------------------------------------------------------
private:
Vector3f positionOfLowerCorner_m;
float size_m;
dword maxItemsPerCell_m;
dword maxLevel_m;
static const dword MAX_LEVEL;
};
/**
* geometric data for the bound of an octree cell.<br/><br/>
*
* constant.<br/><br/>
*
* radius is that of the circumsphere.<br/><br/>
*
* subcell numbering:
* <pre>
* y z 6 7
* |/ 2 3 4 5
* -x 0 1
* </pre>
* in binary:
* <pre>
* y z 110 111
* |/ 010 011 100 101
* -x 000 001
* </pre>
*/
class OctreeBound
{
/// standard object services ---------------------------------------------------
public:
OctreeBound();
OctreeBound( const Vector3f& positionOfLowerCorner,
float size );
OctreeBound( const OctreeBound& parentCellBound,
dword subCellIndex );
~OctreeBound();
OctreeBound( const OctreeBound& );
OctreeBound& operator=( const OctreeBound& );
/// queries --------------------------------------------------------------------
const Vector3f& getLowerCorner() const;
const Vector3f& getUpperCorner() const;
const Vector3f& getCenter() const;
float getRadius() const;
/// fields ---------------------------------------------------------------------
private:
Vector3f positionOfLowerCorner_m;
Vector3f positionOfUpperCorner_m;
Vector3f center_m;
float circumSphereRadius_m;
};
/**
* global and local octree cell data.<br/><br/>
*
* constant.<br/><br/>
*
* to be made during each level of tree descent, so storage is avoided, except
* to hold one at the root.<br/><br/>
*
* subcell numbering:
* <pre>
* y z 6 7
* |/ 2 3 4 5
* -x 0 1
* </pre>
* in binary:
* <pre>
* y z 110 111
* |/ 010 011 100 101
* -x 000 001
* </pre>
*/
class OctreeData
{
/// standard object services ---------------------------------------------------
public:
explicit OctreeData( const OctreeDimensions& dimensions );
OctreeData( const OctreeData& parentCellData,
dword subCellIndex );
OctreeData( const OctreeData&,
const OctreeDimensions& );
~OctreeData();
OctreeData( const OctreeData& );
OctreeData& operator=( const OctreeData& );
/// queries --------------------------------------------------------------------
const OctreeBound& getBound() const;
dword getLevel() const;
const OctreeDimensions& getDimensions() const;
bool isSubdivide( dword itemCount ) const;
/// fields ---------------------------------------------------------------------
private:
/// local to cell
OctreeBound bound_m;
dword level_m;
/// global for octree
const OctreeDimensions* pDimensions_m;
};
/**
* agent abstract base, for Octree implementation use.<br/><br/>
*
* return value of getSubcellOverlapsV is 8 bits, each bit is a bool
* corresponding to a subcell, the high bit for subcell 7, the low bit for
* subcell 0.<br/><br/>
*
* subcell numbering:
* <pre>
* y z 6 7
* |/ 2 3 4 5
* -x 0 1
* </pre>
* in binary:
* <pre>
* y z 110 111
* |/ 010 011 100 101
* -x 000 001
* </pre>
*/
class OctreeAgentV
{
/// standard object services ---------------------------------------------------
protected:
OctreeAgentV() {}
public:
virtual ~OctreeAgentV() {}
private:
OctreeAgentV( const OctreeAgentV& );
OctreeAgentV& operator=( const OctreeAgentV& );
/// queries --------------------------------------------------------------------
public:
virtual bool isOverlappingCellV ( const void* pItem,
const Vector3f& lowerCorner,
const Vector3f& upperCorner ) const =0;
virtual dword getSubcellOverlapsV( const void* pItem,
const Vector3f& lower,
const Vector3f& middle,
const Vector3f& upper ) const =0;
/// constants ------------------------------------------------------------------
static const dword ALL_INSIDE = 0x0000FFFF;
static const dword ALL_OUTSIDE = 0x00000000;
};
/**
* visitor abstract base, for Octree implementation use.<br/><br/>
*
* subcell numbering:
* <pre>
* y z 6 7
* |/ 2 3 4 5
* -x 0 1
* </pre>
* in binary:
* <pre>
* y z 110 111
* |/ 010 011 100 101
* -x 000 001
* </pre>
*
* @see OctreeCell
* @see OctreeBranch
* @see OctreeLeaf
*/
class OctreeVisitorV
{
/// standard object services ---------------------------------------------------
protected:
OctreeVisitorV() {}
public:
virtual ~OctreeVisitorV() {}
private:
OctreeVisitorV( const OctreeVisitorV& );
OctreeVisitorV& operator=( const OctreeVisitorV& );
/// commands -------------------------------------------------------------------
public:
virtual void visitRootV ( const OctreeCell* pRootCell,
const OctreeData& octreeData ) =0;
virtual void visitBranchV( const OctreeCell* subCells[8],
const OctreeData& octreeData ) =0;
virtual void visitLeafV ( const Array<const void*>& items,
const OctreeData& octreeData ) =0;
};
/// inlines ///
/// OctreeDimensions -----------------------------------------------------------
inline
const Vector3f& OctreeDimensions::getPosition() const
{
return positionOfLowerCorner_m;
}
inline
float OctreeDimensions::getSize() const
{
return size_m;
}
inline
dword OctreeDimensions::getMaxItemCountPerCell() const
{
return maxItemsPerCell_m;
}
inline
dword OctreeDimensions::getMaxLevelCount() const
{
return maxLevel_m + 1;
}
/// OctreeBound ----------------------------------------------------------------
inline
const Vector3f& OctreeBound::getLowerCorner() const
{
return positionOfLowerCorner_m;
}
inline
const Vector3f& OctreeBound::getUpperCorner() const
{
return positionOfUpperCorner_m;
}
inline
const Vector3f& OctreeBound::getCenter() const
{
return center_m;
}
inline
float OctreeBound::getRadius() const
{
return circumSphereRadius_m;
}
/// OctreeData -----------------------------------------------------------------
inline
const OctreeBound& OctreeData::getBound() const
{
return bound_m;
}
inline
dword OctreeData::getLevel() const
{
return level_m;
}
inline
const OctreeDimensions& OctreeData::getDimensions() const
{
return *pDimensions_m;
}
inline
bool OctreeData::isSubdivide
(
const dword itemCount
) const
{
return pDimensions_m->isSubdivide( itemCount, level_m );
}
}//namespace
#endif//OctreeAuxiliary_h
#include "OctreeAuxiliary.hpp" /// own header is included last
using namespace hxa7241_graphics;
/// OctreeDimensions ///////////////////////////////////////////////////////////
/// to fit within fp single precision
const dword OctreeDimensions::MAX_LEVEL = 23;
/// standard object services ---------------------------------------------------
OctreeDimensions::OctreeDimensions
(
const Vector3f& positionOfLowerCorner,
const float size,
const dword maxItemsPerCell,
const dword maxLevelCount
)
: positionOfLowerCorner_m( positionOfLowerCorner )
, size_m ( size >= 0.0f ? size : -size )
, maxItemsPerCell_m ( maxItemsPerCell > 0 ? maxItemsPerCell : 1 )
, maxLevel_m ( maxLevelCount > 0 ? maxLevelCount - 1 : 0 )
{
if( maxLevel_m > MAX_LEVEL )
{
maxLevel_m = MAX_LEVEL;
}
}
OctreeDimensions::~OctreeDimensions()
{
}
OctreeDimensions::OctreeDimensions
(
const OctreeDimensions& other
)
// : positionOfLowerCorner_m( other.positionOfLowerCorner_m )
// , size_m ( other.size_m )
// , maxItemsPerCell_m ( other.maxItemsPerCell_m )
// , maxLevel_m ( other.maxLevel_m )
{
OctreeDimensions::operator=( other );
}
OctreeDimensions& OctreeDimensions::operator=
(
const OctreeDimensions& other
)
{
if( &other != this )
{
positionOfLowerCorner_m = other.positionOfLowerCorner_m;
size_m = other.size_m;
maxItemsPerCell_m = other.maxItemsPerCell_m;
maxLevel_m = other.maxLevel_m;
}
return *this;
}
/// queries --------------------------------------------------------------------
bool OctreeDimensions::isSubdivide
(
const dword itemCount,
const dword level
) const
{
return (itemCount > maxItemsPerCell_m) & (level < maxLevel_m);
}
/// OctreeBound ////////////////////////////////////////////////////////////////
/// standard object services ---------------------------------------------------
OctreeBound::OctreeBound()
: positionOfLowerCorner_m( Vector3f::ZERO() )
, positionOfUpperCorner_m( Vector3f::ONE() )
, center_m ( Vector3f::HALF() )
, circumSphereRadius_m ( Vector3f::HALF().length() )
{
}
OctreeBound::OctreeBound
(
const Vector3f& positionOfLowerCorner,
const float size
)
: positionOfLowerCorner_m( positionOfLowerCorner )
, positionOfUpperCorner_m( positionOfLowerCorner +
Vector3f(size, size, size) )
, center_m ( (positionOfLowerCorner_m + positionOfUpperCorner_m)
*= 0.5f )
, circumSphereRadius_m ( (Vector3f::HALF() * size).length() )
{
}
OctreeBound::OctreeBound
(
const OctreeBound& parentCellBound,
const dword subCellIndex
)
{
{
const Vector3f* lowMidHigh[] =
{
&(parentCellBound.positionOfLowerCorner_m),
&(parentCellBound.center_m),
&(parentCellBound.positionOfUpperCorner_m)
};
positionOfLowerCorner_m.setXYZ(
lowMidHigh[ subCellIndex & 1]->getX(),
lowMidHigh[(subCellIndex >> 1) & 1]->getY(),
lowMidHigh[(subCellIndex >> 2) & 1]->getZ() );
positionOfUpperCorner_m.setXYZ(
(lowMidHigh+1)[ subCellIndex & 1]->getX(),
(lowMidHigh+1)[(subCellIndex >> 1) & 1]->getY(),
(lowMidHigh+1)[(subCellIndex >> 2) & 1]->getZ() );
}
((center_m = positionOfLowerCorner_m) += positionOfUpperCorner_m) *= 0.5f;
circumSphereRadius_m = parentCellBound.circumSphereRadius_m * 0.5f;
}
OctreeBound::~OctreeBound()
{
}
OctreeBound::OctreeBound
(
const OctreeBound& other
)
: positionOfLowerCorner_m( other.positionOfLowerCorner_m ),
positionOfUpperCorner_m( other.positionOfUpperCorner_m ),
center_m ( other.center_m ),
circumSphereRadius_m ( other.circumSphereRadius_m )
{
}
OctreeBound& OctreeBound::operator=
(
const OctreeBound& other
)
{
if( &other != this )
{
positionOfLowerCorner_m = other.positionOfLowerCorner_m;
positionOfUpperCorner_m = other.positionOfUpperCorner_m;
center_m = other.center_m;
circumSphereRadius_m = other.circumSphereRadius_m;
}
return *this;
}
/// OctreeData /////////////////////////////////////////////////////////////////
/// standard object services ---------------------------------------------------
OctreeData::OctreeData
(
const OctreeDimensions& dimensions
)
: bound_m ( dimensions.getPosition(), dimensions.getSize() )
, level_m ( 0 )
, pDimensions_m( &dimensions )
{
}
OctreeData::OctreeData
(
const OctreeData& parentCellData,
const dword subCellIndex
)
: bound_m ( parentCellData.bound_m, subCellIndex )
, level_m ( parentCellData.level_m + 1 )
, pDimensions_m( parentCellData.pDimensions_m )
{
}
OctreeData::OctreeData
(
const OctreeData& other,
const OctreeDimensions& dimensions
)
: bound_m ( other.bound_m )
, level_m ( other.level_m )
, pDimensions_m( &dimensions )
{
}
OctreeData::~OctreeData()
{
}
OctreeData::OctreeData
(
const OctreeData& other
)
: bound_m ( other.bound_m )
, level_m ( other.level_m )
, pDimensions_m( other.pDimensions_m )
{
}
OctreeData& OctreeData::operator=
(
const OctreeData& other
)
{
if( &other != this )
{
bound_m = other.bound_m;
level_m = other.level_m;
pDimensions_m = other.pDimensions_m;
}
return *this;
}
#ifndef OctreeImplementation_h
#define OctreeImplementation_h
#include "OctreeAuxiliary.hpp"
#include "Array.hpp"
#include "hxa7241_graphics.hpp"
namespace hxa7241_graphics
{
using hxa7241_graphics::Vector3f;
using hxa7241_general::Array;
class OctreeCell;
/**
* implementation class for the Octree template.
*
* @invariants
* pRootCell_m can be null, or point to an OctreeCell instance.<br/><br/>
*
* at construction, pRootCell_m is set to a legal value.<br/>
* at destruction, pRootCell_m is deleted.<br/>
* whenever pRootCell_m is modified, it must be deleted then set to a legal
* value.<br/>
* a legal value is: either 0, or the value from invocation of 'new'.
*/
class OctreeRoot
{
/// standard object services ---------------------------------------------------
public:
OctreeRoot( const Vector3f& position,
float sizeOfCube,
dword maxItemsPerCell,
dword maxLevelCount );
~OctreeRoot();
OctreeRoot( const OctreeRoot& );
OctreeRoot& operator=( const OctreeRoot& );
/// commands -------------------------------------------------------------------
bool insertItem( const void* pItem,
const OctreeAgentV& agent );
bool removeItem( const void* pItem,
const OctreeAgentV& agent );
/// queries --------------------------------------------------------------------
void visit( OctreeVisitorV& visitor ) const;
bool isEmpty() const;
void getInfo( dword rootWrapperByteSize,
dword& byteSize,
dword& leafCount,
dword& itemCount,
dword& maxDepth ) const;
const Vector3f& getPosition() const;
float getSize() const;
dword getMaxItemCountPerCell() const;
dword getMaxLevelCount() const;
/// fields ---------------------------------------------------------------------
private:
OctreeDimensions dimensions_m;
OctreeCell* pRootCell_m;
};
/**
* abstract base for Composite types, for implementing Octree nodes.
*
* @implementation
* subcell numbering:
* <pre>
* y z 6 7
* |/ 2 3 4 5
* -x 0 1
* </pre>
* in binary:
* <pre>
* y z 110 111
* |/ 010 011 100 101
* -x 000 001
* </pre>
*/
class OctreeCell
{
/// standard object services ---------------------------------------------------
protected:
OctreeCell() {}
public:
virtual ~OctreeCell() {}
private:
OctreeCell( const OctreeCell& );
OctreeCell& operator=( const OctreeCell& );
/// commands -------------------------------------------------------------------
public:
virtual void insertItem( const OctreeData& thisData,
OctreeCell*& pThis,
const void* pItem,
const OctreeAgentV& agent ) =0;
virtual bool removeItem( OctreeCell*& pThis,
const void* pItem,
const dword maxItemsPerCell,
dword& itemCount ) =0;
/// queries --------------------------------------------------------------------
virtual void visit( const OctreeData& thisData,
OctreeVisitorV& visitor ) const =0;
virtual OctreeCell* clone() const =0;
virtual void getInfo( dword& byteSize,
dword& leafCount,
dword& itemCount,
dword& maxDepth ) const =0;
/// statics --------------------------------------------------------------------
static OctreeCell* cloneNonZero( const OctreeCell* );
};
/**
* inner node implementation of an octree cell.<br/><br/>
*
* stores pointers to eight child cells.
*
* @invariants
* subCells_m elements can be null, or point to an OctreeCell instance.
*/
class OctreeBranch
: public OctreeCell
{
/// standard object services ---------------------------------------------------
public:
OctreeBranch();
OctreeBranch( const OctreeData& thisData,
const Array<const void*>& items,
const void* const pItem,
const OctreeAgentV& agent );
virtual ~OctreeBranch();
OctreeBranch( const OctreeBranch& );
OctreeBranch& operator=( const OctreeBranch& );
/// commands -------------------------------------------------------------------
virtual void insertItem( const OctreeData& thisData,
OctreeCell*& pThis,
const void* pItem,
const OctreeAgentV& agent );
virtual bool removeItem( OctreeCell*& pThis,
const void* pItem,
const dword maxItemsPerCell,
dword& itemCount );
/// queries --------------------------------------------------------------------
virtual void visit( const OctreeData& thisData,
OctreeVisitorV& visitor ) const;
virtual OctreeCell* clone() const;
virtual void getInfo( dword& byteSize,
dword& leafCount,
dword& itemCount,
dword& maxDepth ) const;
/// implementation -------------------------------------------------------------
protected:
virtual void zeroSubCells();
/// fields ---------------------------------------------------------------------
private:
OctreeCell* subCells_m[8];
};
/**
* outer node implementation of an octree cell.<br/><br/>
*
* stores pointers to items.
*/
class OctreeLeaf
: public OctreeCell
{
/// standard object services ---------------------------------------------------
public:
OctreeLeaf();
OctreeLeaf( const OctreeLeaf*const leafs[8] );
private:
explicit OctreeLeaf( const void* pItem );
public:
virtual ~OctreeLeaf();
OctreeLeaf( const OctreeLeaf& );
OctreeLeaf& operator=( const OctreeLeaf& );
/// commands -------------------------------------------------------------------
virtual void insertItem( const OctreeData& thisData,
OctreeCell*& pThis,
const void* pItem,
const OctreeAgentV& agent );
virtual bool removeItem( OctreeCell*& pThis,
const void* pItem,
const dword maxItemsPerCell,
dword& itemCount );
/// queries --------------------------------------------------------------------
virtual void visit( const OctreeData& thisData,
OctreeVisitorV& visitor ) const;
virtual OctreeCell* clone() const;
virtual void getInfo( dword& byteSize,
dword& leafCount,
dword& itemCount,
dword& maxDepth ) const;
/// statics --------------------------------------------------------------------
static void insertItemMaybeCreate( const OctreeData& cellData,
OctreeCell*& pCell,
const void* pItem,
const OctreeAgentV& agent );
/// fields ---------------------------------------------------------------------
private:
Array<const void*> items_m;
};
}//namespace
#endif//OctreeImplementation_h
#include "Vector3f.hpp"
#include "OctreeImplementation.hpp" /// own header is included last
using namespace hxa7241_graphics;
/// OctreeRoot /////////////////////////////////////////////////////////////////
/// standard object services ---------------------------------------------------
OctreeRoot::OctreeRoot
(
const Vector3f& position,
const float sizeOfCube,
const dword maxItemsPerCell,
const dword maxLevelCount
)
: dimensions_m( position, sizeOfCube, maxItemsPerCell, maxLevelCount )
, pRootCell_m ( 0 )
{
}
OctreeRoot::~OctreeRoot()
{
delete pRootCell_m;
}
OctreeRoot::OctreeRoot
(
const OctreeRoot& other
)
: dimensions_m( other.dimensions_m )
, pRootCell_m ( OctreeCell::cloneNonZero( other.pRootCell_m ) )
{
}
OctreeRoot& OctreeRoot::operator=
(
const OctreeRoot& other
)
{
if( &other != this )
{
delete pRootCell_m;
pRootCell_m = 0;
pRootCell_m = OctreeCell::cloneNonZero( other.pRootCell_m );
dimensions_m = other.dimensions_m;
}
return *this;
}
/// commands -------------------------------------------------------------------
bool OctreeRoot::insertItem
(
const void* const pItem,
const OctreeAgentV& agent
)
{
bool isInserted = false;
/// make data
const OctreeData data( dimensions_m );
/// check if item overlaps root cell
if( agent.isOverlappingCellV( pItem, data.getBound().getLowerCorner(),
data.getBound().getUpperCorner() ) )
{
OctreeLeaf::insertItemMaybeCreate( data, pRootCell_m, pItem, agent );
isInserted = true;
}
return isInserted;
}
bool OctreeRoot::removeItem
(
const void* const pItem,
const OctreeAgentV& //agent
)
{
bool isRemoved = false;
if( pRootCell_m != 0 )
{
dword unusedBranchItemCount = 0;
isRemoved = pRootCell_m->removeItem( pRootCell_m, pItem,
dimensions_m.getMaxItemCountPerCell(), unusedBranchItemCount );
}
return isRemoved;
}
/// queries --------------------------------------------------------------------
void OctreeRoot::visit
(
OctreeVisitorV& visitor
) const
{
/// make data
const OctreeData data( dimensions_m );
visitor.visitRootV( pRootCell_m, data );
}
bool OctreeRoot::isEmpty() const
{
return pRootCell_m == 0;
}
void OctreeRoot::getInfo
(
const dword rootWrapperByteSize,
dword& byteSize,
dword& leafCount,
dword& itemCount,
dword& maxDepth
) const
{
byteSize = 0;
leafCount = 0;
itemCount = 0;
maxDepth = 0;
if( pRootCell_m != 0 )
{
pRootCell_m->getInfo( byteSize, leafCount, itemCount, maxDepth );
}
byteSize += rootWrapperByteSize;
}
const Vector3f& OctreeRoot::getPosition() const
{
return dimensions_m.getPosition();
}
float OctreeRoot::getSize() const
{
return dimensions_m.getSize();
}
dword OctreeRoot::getMaxItemCountPerCell() const
{
return dimensions_m.getMaxItemCountPerCell();
}
dword OctreeRoot::getMaxLevelCount() const
{
return dimensions_m.getMaxLevelCount();
}
/// OctreeCell /////////////////////////////////////////////////////////////////
/// statics --------------------------------------------------------------------
OctreeCell* OctreeCell::cloneNonZero
(
const OctreeCell* pOriginal
)
{
return (pOriginal != 0) ? pOriginal->clone() : 0;
}
/// OctreeBranch ///////////////////////////////////////////////////////////////
/// standard object services ---------------------------------------------------
OctreeBranch::OctreeBranch()
{
OctreeBranch::zeroSubCells();
}
OctreeBranch::OctreeBranch
(
const OctreeData& thisData,
const Array<const void*>& items,
const void* const pItem,
const OctreeAgentV& agent
)
{
OctreeBranch::zeroSubCells();
try
{
OctreeCell* pNotUsed = 0;
/// insert items
for( int j = items.getLength(); j-- > 0; )
{
OctreeBranch::insertItem( thisData, pNotUsed, items[j], agent );
}
OctreeBranch::insertItem( thisData, pNotUsed, pItem, agent );
}
catch( ... )
{
/// delete any allocated cells
this->~OctreeBranch();
throw;
}
}
OctreeBranch::~OctreeBranch()
{
for( int i = 8; i-- > 0; )
{
delete subCells_m[i];
}
}
OctreeBranch::OctreeBranch
(
const OctreeBranch& other
)
: OctreeCell()
{
OctreeBranch::zeroSubCells();
try
{
for( int i = 8; i-- > 0; )
{
subCells_m[i] = OctreeCell::cloneNonZero( other.subCells_m[i] );
}
}
catch( ... )
{
/// delete any allocated cells
this->~OctreeBranch();
throw;
}
}
OctreeBranch& OctreeBranch::operator=
(
const OctreeBranch& other
)
{
if( &other != this )
{
for( int i = 8; i-- > 0; )
{
delete subCells_m[i];
subCells_m[i] = 0;
subCells_m[i] = OctreeCell::cloneNonZero( other.subCells_m[i] );
}
}
return *this;
}
/// commands -------------------------------------------------------------------
void OctreeBranch::insertItem
(
const OctreeData& thisData,
OctreeCell*& ,//pThis,
const void* const pItem,
const OctreeAgentV& agent
)
{
/// get subcell-item overlaps flags
const OctreeBound& bound = thisData.getBound();
const dword overlaps = agent.getSubcellOverlapsV( pItem,
bound.getLowerCorner(), bound.getCenter(), bound.getUpperCorner() );
/// loop through sub cells
for( int i = 8; i-- > 0; )
{
/// check if sub cell is overlapped by item
if( (overlaps >> i) & 1 )
{
/// make sub cell data
const OctreeData subCellData( thisData, i );
/// add item to sub cell
OctreeLeaf::insertItemMaybeCreate( subCellData, subCells_m[i],
pItem, agent );
}
}
}
bool OctreeBranch::removeItem
(
OctreeCell*& pThis,
const void* const pItem,
const dword maxItemsPerCell,
dword& itemCount
)
{
bool isRemoved = false;
dword branchItemCount = 0;
/// loop through sub cells
for( int i = 8; i-- > 0; )
{
/// remove item from non-null sub cell
OctreeCell*& pSubCell = subCells_m[i];
if( pSubCell != 0 )
{
isRemoved |= pSubCell->removeItem( pSubCell, pItem, maxItemsPerCell,
branchItemCount );
}
}
itemCount += branchItemCount;
/// decide whether to collapse this branch
if( branchItemCount > 0 )
{
/// collapse to leaf
if( branchItemCount <= maxItemsPerCell )
{
/// all subcells *will* be leafs!
/// because:
/// a) if a branch has below it less item refs than the threshold,
/// it collapses to a leaf (this function!)
/// b) the total of item refs below this branch in the tree is less
/// than the threshold
/// c) therefore the total of item refs in any branch below this
/// cell will be less than the threshold
/// d) branchs below this cell will be collapsed before this branch
/// (because the recursive 'removeItem' call is before the
/// collapsing code)
/// so: if this branch will collapse to a leaf, then all its sub
/// branchs (direct and indirect) will collapse to leafs, and that
/// will happen before this branch.
OctreeCell*const pLeaf = new OctreeLeaf(
reinterpret_cast<OctreeLeaf**>( subCells_m ) );
delete pThis;
pThis = pLeaf;
}
}
else
{
/// delete
delete pThis;
pThis = 0;
}
return isRemoved;
}
/// queries --------------------------------------------------------------------
void OctreeBranch::visit
(
const OctreeData& thisData,
OctreeVisitorV& visitor
) const
{
visitor.visitBranchV( const_cast<const OctreeCell**>(subCells_m),
thisData );
}
OctreeCell* OctreeBranch::clone() const
{
return new OctreeBranch( *this );
}
void OctreeBranch::getInfo
(
dword& byteSize,
dword& leafCount,
dword& itemCount,
dword& maxDepth
) const
{
byteSize += sizeof(*this);
const dword thisDepth = maxDepth + 1;
for( int i = 8; i-- > 0; )
{
const OctreeCell*const pSubCell = subCells_m[i];
if( pSubCell != 0 )
{
dword depth = thisDepth;
pSubCell->getInfo( byteSize, leafCount, itemCount, depth );
if( maxDepth < depth )
{
maxDepth = depth;
}
}
}
}
/// implementation -------------------------------------------------------------
void OctreeBranch::zeroSubCells()
{
for( int i = 8; i-- > 0; )
{
subCells_m[i] = 0;
}
}
/// OctreeLeaf /////////////////////////////////////////////////////////////////
/// standard object services ---------------------------------------------------
OctreeLeaf::OctreeLeaf()
: items_m()
{
}
OctreeLeaf::OctreeLeaf
(
const void* pItem
)
: items_m()
{
items_m.append( pItem );
}
OctreeLeaf::OctreeLeaf
(
const OctreeLeaf*const leafs[8]
)
: items_m()
{
/// sum all items lengths
dword totalLength = 0;
for( int i = 8; i-- > 0; )
{
const OctreeLeaf*const pLeaf = leafs[i];
if( 0 != pLeaf )
{
totalLength += pLeaf->items_m.getLength();
}
}
/// prepare items array to hold all other items
items_m.setLength( totalLength );
/// copy items arrays
const void** pElement = items_m.getMemory();
for( int i = 0; i < 8; ++i )
{
const OctreeLeaf*const pLeaf = leafs[i];
if( 0 != pLeaf )
{
const void** pOtherElement = pLeaf->items_m.getMemory();
const void** pOtherEnd = pOtherElement + pLeaf->items_m.getLength();
for( ; pOtherElement < pOtherEnd; ++pOtherElement, ++pElement )
{
*pElement = *pOtherElement;
}
}
}
}
OctreeLeaf::~OctreeLeaf()
{
}
OctreeLeaf::OctreeLeaf
(
const OctreeLeaf& other
)
: OctreeCell()
, items_m( other.items_m )
{
}
OctreeLeaf& OctreeLeaf::operator=
(
const OctreeLeaf& other
)
{
items_m = other.items_m;
return *this;
}
/// commands -------------------------------------------------------------------
void OctreeLeaf::insertItem
(
const OctreeData& thisData,
OctreeCell*& pThis,
const void* const pItem,
const OctreeAgentV& agent
)
{
/// check if item already present
bool isAlreadyPresent = false;
for( int i = items_m.getLength(); (i-- > 0) & !isAlreadyPresent; )
{
isAlreadyPresent |= (items_m[i] == pItem);
}
/// only insert if item not already present
if( !isAlreadyPresent )
{
/// check if leaf should be subdivided
if( !thisData.isSubdivide( items_m.getLength() + 1 ) )
{
/// append item to collection
items_m.append( pItem );
}
else
{
/// subdivide by making branch and adding items to it
OctreeCell*const pBranch = new OctreeBranch( thisData, items_m,
pItem, agent );
/// replace this with branch
delete pThis;
pThis = pBranch;
}
}
}
bool OctreeLeaf::removeItem
(
OctreeCell*& pThis,
const void* const pItem,
const dword ,//maxItemsPerCell,
dword& itemCount
)
{
bool isRemoved = false;
/// loop through items
for( int i = 0; i < items_m.getLength(); )
{
/// check if item is present
if( items_m[i] == pItem )
{
/// remove item
items_m.remove( i );
isRemoved = true;
}
else
{
++i;
}
}
itemCount += items_m.getLength();
/// check if leaf is now empty
if( items_m.isEmpty() )
{
/// remove this leaf
delete pThis;
pThis = 0;
}
return isRemoved;
}
/// queries --------------------------------------------------------------------
void OctreeLeaf::visit
(
const OctreeData& thisData,
OctreeVisitorV& visitor
) const
{
visitor.visitLeafV( items_m, thisData );
}
OctreeCell* OctreeLeaf::clone() const
{
return new OctreeLeaf( *this );
}
void OctreeLeaf::getInfo
(
dword& byteSize,
dword& leafCount,
dword& itemCount,
dword& maxDepth
) const
{
byteSize += sizeof(*this) + (items_m.getLength() * sizeof(void*));
++leafCount;
itemCount += items_m.getLength();
++maxDepth;
}
/// statics --------------------------------------------------------------------
void OctreeLeaf::insertItemMaybeCreate
(
const OctreeData& cellData,
OctreeCell*& pCell,
const void* const pItem,
const OctreeAgentV& agent
)
{
/// check cell exists
if( 0 == pCell )
{
/// make leaf, adding item
OctreeCell*const pLeaf = new OctreeLeaf( pItem );
/// replace cell with leaf
delete pCell;
pCell = pLeaf;
}
else
{
/// forward to existing cell
pCell->insertItem( cellData, pCell, pItem, agent );
}
}
The source code is available according to the following license:
———
Copyright (c) 2004-2005, Harrison Ainsworth / HXA7241.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, provided that the above copyright notice(s) and this permission notice appear in all copies of the Software and that both the above copyright notice(s) and this permission notice appear in supporting documentation.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization of the copyright holder.