Class providing building methods for k-dimensional trees with surface area heuristic (SAH)
More...
|
| SAHKDTreeFactory (size_t const lossNodes=21, double const ci=1, double const cl=1, double const co=1) |
| Surface area heuristic KDTree factory default constructor. More...
|
|
KDTreeFactory * | clone () const override |
|
void | _clone (KDTreeFactory *kdtf) const override |
| Assign attributes from SAHKDTreeFactory to its clone.
|
|
void | defineSplit (KDTreeNode *node, KDTreeNode *parent, vector< Primitive * > &primitives, int const depth) const override |
| Define the split axis and position for current node. More...
|
|
void | computeKDTreeStats (KDTreeNodeRoot *root) const override |
| Compute the simple stats of the KDTree but also its cost based on surface area heuristic defined in defineSplit function. More...
|
|
void | buildChildrenNodes (KDTreeNode *node, KDTreeNode *parent, vector< Primitive * > const &primitives, int const depth, int const index, vector< Primitive * > &leftPrimitives, vector< Primitive * > &rightPrimitives) override |
| Build children nodes using \(C_T\) heuristic to handle KDTree in-depth partitioning. More...
|
|
virtual void | buildChildrenNodesRecipe (KDTreeNode *node, KDTreeNode *parent, vector< Primitive * > const &primitives, int const depth, int const index, vector< Primitive * > &leftPrimitives, vector< Primitive * > &rightPrimitives, std::function< void(KDTreeNode *node, int const depth, int const index, vector< Primitive * > &leftPrimitives, vector< Primitive * > &rightPrimitives)>f_buildChildrenNodes) |
| The recipe for building of children nodes by SAH algorithm. It is meant to be used by the SAHKDTreeFactory::buildChildrenNodes but also by any alternative implementation which shared the same recipe (global logic) but changes the way some parts are computed. For instance, it is used to handle geometry-level parallelization. More...
|
|
virtual size_t | getLossNodes () const |
| Obtain the loss nodes used to compute the Surface Area Heuristic. More...
|
|
virtual void | setLossNodes (size_t const lossNodes) |
| Set the number loss nodes used to compute the Surface Area Heuristic. More...
|
|
virtual double | getInteriorCost () const |
| Obtain the cost-weight of interior nodes. More...
|
|
virtual void | setInteriorCost (double const ci) |
| Set the cost-weight of interior nodes. More...
|
|
virtual double | getLeafCost () const |
| Obtain the cost-weight for leaf nodes. More...
|
|
virtual void | setLeafCost (double const cl) |
| Set the cost-weight for leaf nodes. More...
|
|
virtual double | getObjectCost () const |
| Obtain the cost-weight of testing an object for intersection. More...
|
|
virtual void | setObjectCost (double const co) |
| Set the cost-weight of testing an object for intersection. More...
|
|
| SimpleKDTreeFactory () |
| SimpleKDTreeFactory default constructor. More...
|
|
KDTreeFactory * | clone () const override |
|
KDTreeNodeRoot * | makeFromPrimitivesUnsafe (vector< Primitive * > &primitives, bool const computeStats=false, bool const reportStats=false) override |
| Build a simple KDTree from given primitives. More...
|
|
| KDTreeFactory () |
| K dimensional tree factory default constructor.
|
|
virtual KDTreeNodeRoot * | makeFromPrimitives (vector< Primitive * > const &primitives, bool const computeStats=false, bool const reportStats=false) |
| Safe wrapper from makeFromPrimitivesUnsafe which handles a copy to make from primitives by default. This function behavior might be overridden by any derived/child class. It is expected that any implementation of makeFromPrimitives provides a way to implement the makeFromPrimitivesUnsafe method without modifying vector of input primitives. Notice this does not mean primitives themselves cannot be modified, that depends on the type of KDTreeFactory. It only means that the vector itself will not be modified, for instance due to sorting purposes. More...
|
|
virtual bool | isBuildingLightNodes () |
| Check if KDTreeFactory is building light nodes. More...
|
|
virtual void | setBuildingLightNodes (bool const buildLightNodes) |
| Set KDTreeFactory so it build light nodes (true) or not (false) More...
|
|
virtual void | setChild (LightKDTreeNode *&child, KDTreeNode *node) |
| Set child to given node if and only if node is not null. It must be used to assign children nodes in a thread-safe way. More...
|
|
|
bool | checkNodeMustSplit (vector< Primitive * > const &primitives, vector< Primitive * > const &leftPrimitives, vector< Primitive * > const &rightPrimitives) const override |
| Check wheter the node must be splitted (true) or not (false) depending on its total primitives. More...
|
|
virtual double | splitLoss (vector< Primitive * > const &primitives, int const splitAxis, double const splitPos, double const r) const |
| Compute the loss function for the splitting hyperplane. More...
|
|
virtual void | computeBestSplit (vector< Primitive * > &primitives, size_t const lossNodes, double const start, double const step, int const splitAxis, double const minBound, double const boundLength, double &loss, double &splitPos) const |
| Iteratively compute the best split position, it is the one with smaller loss. More...
|
|
virtual double | findSplitPositionBySAH (KDTreeNode *node, vector< Primitive * > &primitives) const |
| Find the best split position using Surface Area Heuristic (SAH) as described in SAHKDTreeFactory::defineSplit. More...
|
|
virtual double | findSplitPositionBySAHRecipe (KDTreeNode *node, vector< Primitive * > &primitives, std::function< void(vector< Primitive * >::iterator begin, vector< Primitive * >::iterator end, KDTreePrimitiveComparator comparator)> f_sortPrimitives, std::function< void(vector< Primitive * > &primitives, size_t const lossNodes, double const start, double const step, int const splitAxis, double const minBound, double const boundLength, double &loss, double &splitPos)> f_computeLossNodes) const |
| The recipe for finding split position by SAH algorithm. It is meant to be used by the SAHKDTreeFactory::findSplitPositionBySAH but also by any alternative implementation which shares the same recipe ( global logic) but changes the way some parts are computed. For instance, it is used to handle geometry-level parallelization. More...
|
|
virtual double | heuristicILOT (double &hi, double &hl, double &ho, double &ht, double const surfaceAreaRoot, double const surfaceAreaInterior, double const surfaceAreaLeaf, vector< Primitive * > const &primitives) const |
| Compute the \(C_T\) heuristic preserving partials result of interest. More...
|
|
virtual double | cumulativeILOT (double &hi, double &hl, double &ho, double &ht, double const _hi, double const _hl, double const _ho, double const saRoot) const |
| Compute the cumulative of \(C_T\) heuristic ILOT. More...
|
|
virtual void | internalizeILOT (double &hi, double &hl, double &ho, double &ht, KDTreeNode *node, vector< Primitive * > const &primitives, vector< Primitive * > const &leftPrimitives, vector< Primitive * > const &rightPrimitives) |
| Compute ILOT corresponding to internalization (make interior) of given node and its corresponding left and right splits. More...
|
|
virtual void | toILOTCache (double const I, double const L, double const O, double const T) |
| Set ILOT cache from given values. More...
|
|
virtual void | fromILOTCache (double &I, double &L, double &O, double &T) const |
| Set references from ILOT cache. More...
|
|
virtual void | fromILOCache (double &I, double &L, double &O) const |
| Set references from ILOT cache but only for ILO components. More...
|
|
virtual double | getCacheT () const |
| Obtain the T component of ILOT cache. More...
|
|
virtual void | initILOT (KDTreeNode *root, vector< Primitive * > const &primitives) |
| Initialize the ILOT cache from given root node. More...
|
|
virtual void | setCacheRoot (KDTreeNode *root) |
| Set the cached root node. More...
|
|
virtual KDTreeNode * | buildRecursive (KDTreeNode *parent, bool const left, vector< Primitive * > &primitives, int const depth, int const index) |
| Recursively build a KDTree for given primitives. More...
|
|
virtual KDTreeNode * | buildRecursiveRecipe (KDTreeNode *parent, bool const left, vector< Primitive * > &primitives, int const depth, int const index, std::function< void(KDTreeNode *node, KDTreeNode *parent, bool const left, vector< Primitive * > const &primitives)> f_computeNodeBoundaries, std::function< void(KDTreeNode *node, KDTreeNode *parent, vector< Primitive * > &primitives, int const depth)> f_defineSplit, std::function< void(vector< Primitive * > const &primitives, int const splitAxis, double const splitPos, vector< Primitive * > &leftPrimitives, vector< Primitive * > &rightPrimitives)> f_populateSplits, std::function< void(KDTreeNode *node, KDTreeNode *parent, vector< Primitive * > const &primitives, int const depth, int const index, vector< Primitive * > &leftPrimitives, vector< Primitive * > &rightPrimitives)> f_buildChildrenNodes) |
| The recipe of the recursive building algorithm. It is meant to be used by the SimpleKDTreeFactory::buildRecursive but also by any alternative implementation which shares the same recipe (global logic) but changes the way some parts are computed. For instance, it is used by the MultiThreadKDTreeFactory to handle geometry-level parallelization. More...
|
|
virtual void | reportKDTreeStats (KDTreeNodeRoot *root, vector< Primitive * > const &primitives) const |
| Report KDTree stats of given root node at INFO logging level. More...
|
|
virtual void | populateSplits (vector< Primitive * > const &primitives, int const splitAxis, double const splitPos, vector< Primitive * > &leftPrimitives, vector< Primitive * > &rightPrimitives) const |
| Populate list of primitives for left and right splits from given primitives of node being splitted. More...
|
|
virtual void | computeNodeBoundaries (KDTreeNode *node, KDTreeNode *parent, bool const left, vector< Primitive * > const &primitives) const |
| Compute min and max position and surface area of bounding cuboid for given node. More...
|
|
virtual void | onPopulateSplitsDigestPrimitive (Primitive *p, int const splitAxis, double const splitPos, vector< Primitive * > &leftPrimitives, vector< Primitive * > &rightPrimitives) const |
| Function to assist SimpleKDTreeFactory::populateSplits by providing the logic of digesting a primitive. More...
|
|
virtual void | computeMinMaxSAHForChild (KDTreeNode *node, KDTreeNode *parent, bool const left, vector< Primitive * > const &primitives) const |
| Function to assist SimpleKDTreeFactory::computeNodeBoundaries when computing surface area heuristic and minimum and maximum positions for child nodes. More...
|
|
virtual void | onRootBoundariesDigestPrimitive (Primitive *primitive, double &ax, double &ay, double &az, double &bx, double &by, double &bz) const |
| Function to assist SimpleKDTreeFactory::computeNodeBoundaries by providing the logic of digesting a primitive. More...
|
|
virtual void | onComputeNodeBoundariesCalcSAH (KDTreeNode *node, double const ax, double const ay, double const az, double const bx, double const by, double const bz) const |
| Function to assist SimpleKDTreeFactory::computeNodeBoundaries when computing the SAH for a node. More...
|
|
virtual void | makeLeaf (KDTreeNode *node, vector< Primitive * > const &primitives) const |
| Make given node a leaf one. More...
|
|
virtual void | lighten (KDTreeNodeRoot *root) |
| Rebuild all children of given root KDTree node as LightKDTeeeNode nodes. More...
|
|
virtual LightKDTreeNode * | _lighten (KDTreeNode *node) |
| Assist KDTreeFactory::lighten function by handling the lighten of a given non-root node. More...
|
|
|
size_t | lossNodes |
| How many loss nodes must be computed when optimizing the loss function \(\mathcal{L}_2\) to determine the best split position for a given KDTree node. More...
|
|
double | ci |
| Cost-weight for traversing interior nodes. More...
|
|
double | cl |
| Cost-weight for traversing leaf nodes. More...
|
|
double | co |
| Cost-weight for testing an object for intersection. More...
|
|
KDTreeNode * | cacheRoot = nullptr |
| Cache pointer to root node of current KDTree being built.
|
|
double | cacheI |
| Cache last valid interior cost. More...
|
|
double | cacheL |
| Cache last valid leaves cost. More...
|
|
double | cacheO |
| Cache last valid object cost. More...
|
|
double | cacheT |
| Cache last valid tree cost. More...
|
|
std::function< void(void)> | _lockILOT |
| Function to lock the ILOT cache on a unique way (no thread but locker one must be able to use it). By default it is a void function, it must be overridden to provide concurrency handling. More...
|
|
std::function< void(void)> | _unlockILOT |
| Function to unlock the ILOT cache. It is the counterpart of the _lockILOT function. More...
|
|
std::function< KDTreeNode *(KDTreeNode *, bool const, vector< Primitive * > &, int const, int const)> | _buildRecursive |
| The member function as attribute used to recursively build KDTree nodes. By default it will be assigned to the buildRecursive member function but it might be overridden by other implementations. For instance, to wrap the buildRecursive behavior to handle parallel building of KDTrees. More...
|
|
size_t | minSplitPrimitives |
| How many primitives are required for a node to be splitted. More...
|
|
bool | buildLightNodes = true |
| When it is true, the KDTreeFactory is expected to build light nodes. It is, built KDTree must have a KDTreeRootNode which children are all LightKDTreeNode. When it is false, KDTreeFactory is allowed to build KDTree with KDTreeNode children, which might require more memory. More...
|
|
LightKDTreeNodeBlockAllocator | lkdtnBlockAllocator |
| The block allocator to speed-up lighten of KDTree by reducing allocation calls when instantiating multiple LightKDTreeNode. More...
|
|
Class providing building methods for k-dimensional trees with surface area heuristic (SAH)
- Author
- Alberto M. Esmoris Pena
- Version
- 1.0
The surface area heuristic KDTree factory defines the split point from the fact that an optimal performance point must be between the median of the distribution and the geometric center.
Let \(C_i\), \(C_l\) and \(C_o\) be the cost-weight for traversing interior nodes, traversing leaf nodes and testing an object for intersection respectively. Also, let \(N_i\), \(N_l\) and \(N_o\) number of interior nodes, number of leaf nodes and number of objects. Considering \(S_A(i)\) the surface area of the i-th interior node, \(S_A(l)\) the surface area of the l-th leaf node and generically \(S_A(x)\) the surface area of the \(x\) object or the \(x\) set of objects. It is now possible to define the cost of the tree where \(R\) is the root node as follows, where \(N_o(l)\) is the number of objects in the \(l\)-th leaf:
\[ C_T = \frac{ C_i \sum_{i=1}^{N_i}{S_A(i)} + C_l \sum_{l=1}^{N_l}{S_A(l)} + C_o \sum_{l=1}^{N_l}{S_A(l) N_o(l)} } {S_A(R)} \]
Alternatively, let \(S_l(o)\) be the set of leaves in which object \(o\) resides so the cost of the tree can be also defined as:
\[ C'_T = \frac{ C_i \sum_{i=1}^{N_i}{S_A(i)} + C_l \sum_{l=1}^{N_l}{S_A(l)} + C_o \sum_{o=1}^{N_o}{S_A[S_l(o)]} } {S_A(R)} \]
The main difference between \(C_T\) and \(C'_T\) would be that the second one fits better the case where a ray is not going to intersect multiple times the same object.
Now, let \(r\) be the normalized position of the splitting hyperplane for node \(N\) so \(r=0\) is the lower limit, \(r=1\) is the upper limit and \(r=\frac{1}{2}\) is the center. Moreover, let \(L_r\) and \(R_r\) be the left and right parts for the \(r\) split position and \(N_o(L_r)\) and \(N_o(R_r)\) be the number of objects at the left and right splits respectively. In consequence, following loss function arises:
\[ \mathcal{L}(r) = S_A(L_r)N_o(L_r) + S_A(R_r)N_o(R_r) - S_A(N)N_o(N) \]
Alternatively, considering the term \(-S_A(N)N_o(N)\) is the amount of work saved by making the node an interior one (so the minus sign), it can be treated as a constant so for the sake of simplicity it would lead to:
\[ \begin{array}{lll} \mathcal{L}(r) &=& S_A(L_r)N_o(L_r) + S_A(R_r)N_o(R_r) \\ &=& rS_A(N)N_o(L_r) + (1-r)S_A(N)N_o(R_r) \\ &=& S_A(N) \left[rN_o(L_r) + (1-r)N_o(R_r)\right] \end{array} \]
Differentiating with respect to \(r\) leads to:
\[ \frac{d\mathcal{L}}{dr} = \left[2N_o(L_r) - N_o(N)\right]\frac{d}{dr}S_A(L_r) + \left[ S_A(L_r) - S_A(R_r) \right] \frac{d}{dr}N_o(L_r) \]
Although \(N_o(L_r)\) is a discontinuous function, which implies \(\frac{d}{dr}N_o(L_r)\) is not defined, it is known that is always nonnegative which is enough to define a valid minimization criteria. In consequence, it is possible to analyze different scenarios. First, consider the case where the median lies somewhere satisfying \(r < \frac{1}{2}\). Thus, \(\frac{d}{dr}\mathcal{L}(r) < 0\) at the left side of the object median because \(N_o(L_r) < \frac{N_o(N)}{2}\) and \(S_A(L_r) < S_A(R_r)\). On the other hand, \(\frac{d}{dr}\mathcal{L}(r) > 0\) at the right side of the spatial median because \(N_o(L_r) > \frac{N_o(N)}{2}\) and \(S_A(L_r) > S_A(R_r)\). So the minimum must occur between the object median and the spatial median if the object median is to the left of the spatial median. It is easy to see that an analogous argument applies for the case where the object median is to the right of the spatial median. Then, the optimum split must lie between the object median and the spatial median (center).
To clarify, the object median is understood as related to the splitting plane that places one half of the objects on each side of the plane. While the spatial median \(\mu\) for a given KDTree node in \(\mathbb{R}^{n}\) with \(a = (a_1, \ldots, a_n)\) as minimum vertex and \(b = (b_1, \ldots, b_n)\) as maximum vertex is:
\[ \mu = \frac{a+b}{2} = \left(\frac{a_1+b_1}{2}, \ldots, \frac{a_n+b_n}{2}\right) \]
For a more detailed explanation refer to "Heuristics for ray tracing
using space subdivision" by J. David MacDonald and Kellogg S. Booth.
- See also
- SimpleKDTreeFactory
-
AxisSAHKDTreeFactory
void SAHKDTreeFactory::buildChildrenNodes |
( |
KDTreeNode * |
node, |
|
|
KDTreeNode * |
parent, |
|
|
vector< Primitive * > const & |
primitives, |
|
|
int const |
depth, |
|
|
int const |
index, |
|
|
vector< Primitive * > & |
leftPrimitives, |
|
|
vector< Primitive * > & |
rightPrimitives |
|
) |
| |
|
overridevirtual |
Build children nodes using \(C_T\) heuristic to handle KDTree in-depth partitioning.
If root node, then by default assume it is a leaf node containing all primitives:
\[ t_0 : \mathrm{ILOT} = C_T = \frac{1}{S_A(R)} \left[ C_lS_A(R) + C_oS_A(R)N_o(R) \right] \]
After this, speculate its cost if it is make an interior node with its primitives being splitted into 2 leaf nodes:
\[ C_{Sr} = \frac{1}{S_A(R)} \left[ C_i S_A(R) + C_l \sum_{l=1}^{2} {S_A(l)} + C_o \sum_{l=1}^{2} {S_A(l)N_o(l)} \right] \]
Now if \(C_{Sr} \geq C_T\) at \(t_0\) then the process is stopped and all primitives remain in the root node. Otherwise, \(t_1\) happend so:
\[ t_1 : \mathrm{ILOT} = C_T = C_{Sr} \]
After the initial case, a similar process is recursively applied to each new node until \(C_{Si} \geq C_{T}\). It is, until the speculative cost is found to be greater or equal than current cost. This new speculative cost for non-root interior nodes is computed as follows, where \(N\) is current leaf node which might be splitted depending on analysis and \(L_r\) and \(R_r\) are its left and right splits respectively:
\[ \left\{\begin{array}{lll} k_1 &=& C_i S_A(N) \\ k_2 &=& C_l \left(S_A(L_r) + S_A(R_r)\right) - C_l S_A(N) \\ k_3 &=& C_o \left(S_A(L_r)N_o(L_r) + S_A(R_r)N_o(R_r)\right) - C_o S_A(N)N_o(N) \\ C_{Si} &=& \frac{1}{S_A(R)} \left[ C_i \sum_{i=1}^{N_i} S_A(i) + k_1 + C_l \sum_{l=1}^{N_l} S_A(l) + k_2 + C_o \sum_{l=1}^{N_l} S_A(l)N_o(l) + k_3 \right] \end{array}\right. \]
Thus, iterations \(x>1\) that will only happen when \(C_{Si} < C_T\) can be defined as:
\[ t_{x>1} : \mathrm{ILOT} = C_T = C_{Si} \]
- See also
- SimpleKDTreeFactory::buildChildrenNodes
-
SAHKDTreeFactory::defineSplit
-
SAHKDTreeFactory::computeKDTreeStats
Reimplemented from SimpleKDTreeFactory.