celestia/src/celengine/octree.h

399 lines
12 KiB
C++

// octree.h
//
// Octree-based visibility determination for objects.
//
// Copyright (C) 2001-2009, Celestia Development Team
// Original version by Chris Laurel <claurel@gmail.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
#ifndef _CELENGINE_OCTREE_H_
#define _CELENGINE_OCTREE_H_
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <celmath/plane.h>
#include <celengine/observer.h>
#include <vector>
// The DynamicOctree and StaticOctree template arguments are:
// OBJ: object hanging from the node,
// PREC: floating point precision of the culling operations at node level.
// The hierarchy of octree nodes is built using a single precision value (excludingFactor), which relates to an
// OBJ's limiting property defined by the octree particular specialization: ie. we use [absolute magnitude] for star octrees, etc.
// For details, see notes below.
template <class OBJ, class PREC> class OctreeProcessor
{
public:
OctreeProcessor() {};
virtual ~OctreeProcessor() {};
virtual void process(const OBJ& obj, PREC distance, float appMag) = 0;
};
struct OctreeLevelStatistics
{
unsigned int nodeCount;
unsigned int objectCount;
double size;
};
template <class OBJ, class PREC> class StaticOctree;
template <class OBJ, class PREC> class DynamicOctree
{
public:
typedef Eigen::Matrix<PREC, 3, 1> PointType;
private:
typedef std::vector<const OBJ*> ObjectList;
typedef bool (LimitingFactorPredicate) (const OBJ&, const float);
typedef bool (StraddlingPredicate) (const Eigen::Matrix<PREC, 3, 1>&, const OBJ&, const float);
typedef PREC (ExclusionFactorDecayFunction)(const PREC);
public:
DynamicOctree(const Eigen::Matrix<PREC, 3, 1>& cellCenterPos,
const float exclusionFactor);
~DynamicOctree();
void insertObject (const OBJ&, const PREC);
void rebuildAndSort(StaticOctree<OBJ, PREC>*&, OBJ*&);
private:
static unsigned int SPLIT_THRESHOLD;
static LimitingFactorPredicate* limitingFactorPredicate;
static StraddlingPredicate* straddlingPredicate;
static ExclusionFactorDecayFunction* decayFunction;
private:
void add (const OBJ&);
void split(const PREC);
void sortIntoChildNodes();
DynamicOctree* getChild(const OBJ&, const Eigen::Matrix<PREC, 3, 1>&);
DynamicOctree** _children;
Eigen::Matrix<PREC, 3, 1> cellCenterPos;
PREC exclusionFactor;
ObjectList* _objects;
};
template <class OBJ, class PREC> class StaticOctree
{
friend class DynamicOctree<OBJ, PREC>;
public:
typedef Eigen::Matrix<PREC, 3, 1> PointType;
public:
StaticOctree(const PointType& cellCenterPos,
const float exclusionFactor,
OBJ* _firstObject,
unsigned int nObjects);
~StaticOctree();
// These methods are only declared at the template level; we'll implement them as
// full specializations, allowing for different traversal strategies depending on the
// object type and nature.
// This method searches the octree for objects that are likely to be visible
// to a viewer with the specified obsPosition and limitingFactor. The
// octreeProcessor is invoked for each potentially visible object --no object with
// a property greater than limitingFactor will be processed, but
// objects that are outside the view frustum may be. Frustum tests are performed
// only at the node level to optimize the octree traversal, so an exact test
// (if one is required) is the responsibility of the callback method.
void processVisibleObjects(OctreeProcessor<OBJ, PREC>& processor,
const PointType& obsPosition,
const Eigen::Hyperplane<PREC, 3>* frustumPlanes,
float limitingFactor,
PREC scale) const;
void processCloseObjects(OctreeProcessor<OBJ, PREC>& processor,
const PointType& obsPosition,
PREC boundingRadius,
PREC scale) const;
int countChildren() const;
int countObjects() const;
void computeStatistics(std::vector<OctreeLevelStatistics>& stats, unsigned int level = 0);
private:
static const PREC SQRT3;
private:
StaticOctree** _children;
Eigen::Matrix<PREC, 3, 1> cellCenterPos;
float exclusionFactor;
OBJ* _firstObject;
unsigned int nObjects;
};
// There are two classes implemented in this module: StaticOctree and
// DynamicOctree. The DynamicOctree is built first by inserting
// objects from a database or catalog and is then 'compiled' into a StaticOctree.
// In the process of building the StaticOctree, the original object database is
// reorganized, with objects in the same octree node all placed adjacent to each
// other. This spatial sorting of the objects dramatically improves the
// performance of octree operations through much more coherent memory access.
enum
{
XPos = 1,
YPos = 2,
ZPos = 4,
};
// The SPLIT_THRESHOLD is the number of objects a node must contain before its
// children are generated. Increasing this number will decrease the number of
// octree nodes in the tree, which will use less memory but make culling less
// efficient.
template <class OBJ, class PREC>
inline DynamicOctree<OBJ, PREC>::DynamicOctree(const Eigen::Matrix<PREC, 3, 1>& cellCenterPos,
const float exclusionFactor):
_children (nullptr),
cellCenterPos (cellCenterPos),
exclusionFactor(exclusionFactor),
_objects (nullptr)
{
}
template <class OBJ, class PREC>
inline DynamicOctree<OBJ, PREC>::~DynamicOctree()
{
if (_children != nullptr)
{
for (int i = 0; i < 8; ++i)
{
delete _children[i];
}
delete[] _children;
}
delete _objects;
}
template <class OBJ, class PREC>
inline void DynamicOctree<OBJ, PREC>::insertObject(const OBJ& obj, const PREC scale)
{
// If the object can't be placed into this node's children, then put it here:
if (limitingFactorPredicate(obj, exclusionFactor) || straddlingPredicate(cellCenterPos, obj, exclusionFactor) )
add(obj);
else
{
// If we haven't allocated child nodes yet, try to fit
// the object in this node, even though it could be put
// in a child. Only if there are more than SPLIT_THRESHOLD
// objects in the node will we attempt to place the
// object into a child node. This is done in order
// to avoid having the octree degenerate into one object
// per node.
if (_children == nullptr)
{
// Make sure that there's enough room left in this node
if (_objects != nullptr && _objects->size() >= DynamicOctree<OBJ, PREC>::SPLIT_THRESHOLD)
split(scale * 0.5f);
add(obj);
}
else
// We've already allocated child nodes; place the object
// into the appropriate one.
this->getChild(obj, cellCenterPos)->insertObject(obj, scale * (PREC) 0.5);
}
}
template <class OBJ, class PREC>
inline void DynamicOctree<OBJ, PREC>::add(const OBJ& obj)
{
if (_objects == nullptr)
_objects = new ObjectList;
_objects->push_back(&obj);
}
template <class OBJ, class PREC>
inline void DynamicOctree<OBJ, PREC>::split(const PREC scale)
{
_children = new DynamicOctree*[8];
for (int i = 0; i < 8; ++i)
{
Eigen::Matrix<PREC, 3, 1> centerPos = cellCenterPos;
centerPos += Eigen::Matrix<PREC, 3, 1>(((i & XPos) != 0) ? scale : -scale,
((i & YPos) != 0) ? scale : -scale,
((i & ZPos) != 0) ? scale : -scale);
#if 0
centerPos.x += ((i & XPos) != 0) ? scale : -scale;
centerPos.y += ((i & YPos) != 0) ? scale : -scale;
centerPos.z += ((i & ZPos) != 0) ? scale : -scale;
#endif
_children[i] = new DynamicOctree(centerPos,
decayFunction(exclusionFactor));
}
sortIntoChildNodes();
}
// Sort this node's objects into objects that can remain here,
// and objects that should be placed into one of the eight
// child nodes.
template <class OBJ, class PREC>
inline void DynamicOctree<OBJ, PREC>::sortIntoChildNodes()
{
unsigned int nKeptInParent = 0;
for (unsigned int i=0; i<_objects->size(); ++i)
{
const OBJ& obj = *(*_objects)[i];
if (limitingFactorPredicate(obj, exclusionFactor) ||
straddlingPredicate(cellCenterPos, obj, exclusionFactor) )
{
(*_objects)[nKeptInParent++] = (*_objects)[i];
}
else
{
this->getChild(obj, cellCenterPos)->add(obj);
}
}
_objects->resize(nKeptInParent);
}
template <class OBJ, class PREC>
inline void DynamicOctree<OBJ, PREC>::rebuildAndSort(StaticOctree<OBJ, PREC>*& _staticNode, OBJ*& _sortedObjects)
{
OBJ* _firstObject = _sortedObjects;
if (_objects != nullptr)
for (typename ObjectList::const_iterator iter = _objects->begin(); iter != _objects->end(); ++iter)
{
*_sortedObjects++ = **iter;
}
unsigned int nObjects = (unsigned int) (_sortedObjects - _firstObject);
_staticNode = new StaticOctree<OBJ, PREC>(cellCenterPos, exclusionFactor, _firstObject, nObjects);
if (_children != nullptr)
{
_staticNode->_children = new StaticOctree<OBJ, PREC>*[8];
for (int i=0; i<8; ++i)
_children[i]->rebuildAndSort(_staticNode->_children[i], _sortedObjects);
}
}
//MS VC++ wants this to be placed here:
template <class OBJ, class PREC>
const PREC StaticOctree<OBJ, PREC>::SQRT3 = (PREC) 1.732050807568877;
template <class OBJ, class PREC>
inline StaticOctree<OBJ, PREC>::StaticOctree(const Eigen::Matrix<PREC, 3, 1>& cellCenterPos,
const float exclusionFactor,
OBJ* _firstObject,
unsigned int nObjects):
_children (nullptr),
cellCenterPos (cellCenterPos),
exclusionFactor(exclusionFactor),
_firstObject (_firstObject),
nObjects (nObjects)
{
}
template <class OBJ, class PREC>
inline StaticOctree<OBJ, PREC>::~StaticOctree()
{
if (_children != nullptr)
{
for (int i = 0; i < 8; ++i)
delete _children[i];
delete[] _children;
}
}
template <class OBJ, class PREC>
inline int StaticOctree<OBJ, PREC>::countChildren() const
{
int count = 0;
for (int i = 0; i < 8; ++i)
count += _children != nullptr ? 1 + _children[i]->countChildren() : 0;
return count;
}
template <class OBJ, class PREC>
inline int StaticOctree<OBJ, PREC>::countObjects() const
{
int count = nObjects;
if (_children != nullptr)
for (int i = 0; i < 8; ++i)
count += _children[i]->countObjects();
return count;
}
template <class OBJ, class PREC>
void StaticOctree<OBJ, PREC>::computeStatistics(std::vector<OctreeLevelStatistics>& stats, unsigned int level)
{
if (level >= stats.size())
{
while (level >= stats.size())
{
OctreeLevelStatistics levelStats;
levelStats.nodeCount = 0;
levelStats.objectCount = 0;
levelStats.size = 0.0;
stats.push_back(levelStats);
}
}
stats[level].nodeCount++;
stats[level].objectCount += nObjects;
stats[level].size = 0.0;
if (_children != nullptr)
{
for (int i = 0; i < 8; i++)
_children[i]->computeStatistics(stats, level + 1);
}
}
#endif // _OCTREE_H_