//////////////////////////////////////////////////////////////////// // Octree.inl // // Copyright 2007 cDc@seacave // Distributed under the Boost Software License, Version 1.0 // (See http://www.boost.org/LICENSE_1_0.txt) // D E F I N E S /////////////////////////////////////////////////// #ifdef _USE_OPENMP // minimum number of polygons for which we do multi-threading #define OCTREE_MIN_ITEMS_MINTHREAD 1024*2 #endif // S T R U C T S /////////////////////////////////////////////////// template inline TOctree::CELL_TYPE::CELL_TYPE() : m_child(NULL) { } // constructor template inline TOctree::CELL_TYPE::~CELL_TYPE() { delete[] m_child; } // destructor /*----------------------------------------------------------------*/ template inline void TOctree::CELL_TYPE::Release() { delete[] m_child; m_child = NULL; } // Release // swap the two octrees template inline void TOctree::CELL_TYPE::Swap(CELL_TYPE& rhs) { std::swap(m_child, rhs.m_child); if (IsLeaf()) std::swap(m_leaf, rhs.m_leaf); else std::swap(m_node, rhs.m_node); } // Swap /*----------------------------------------------------------------*/ // compute item's index corresponding to the containing cell template inline unsigned TOctree::CELL_TYPE::ComputeChild(const POINT_TYPE& item) const { ASSERT(!IsLeaf()); unsigned idx = 0; if (item[0] >= Node().center[0]) idx |= (1<<0); if (DIMS > 1) if (item[1] >= Node().center[1]) idx |= (1<<1); if (DIMS > 2) if (item[2] >= Node().center[2]) idx |= (1<<2); return idx; } // ComputeChild /*----------------------------------------------------------------*/ template void TOctree::CELL_TYPE::ComputeCenter(POINT_TYPE centers[]) { if (DIMS == 1) { centers[0] << -1; centers[1] << 1; } else if (DIMS == 2) { centers[0] << -1,-1; centers[1] << 1,-1; centers[2] << -1, 1; centers[3] << 1, 1; } else if (DIMS == 3) { centers[0] << -1,-1,-1; centers[1] << 1,-1,-1; centers[2] << -1, 1,-1; centers[3] << 1, 1,-1; centers[4] << -1,-1, 1; centers[5] << 1,-1, 1; centers[6] << -1, 1, 1; centers[7] << 1, 1, 1; } } // ComputeCenter /*----------------------------------------------------------------*/ template inline typename TOctree::POINT_TYPE TOctree::CELL_TYPE::ComputeChildCenter(const POINT_TYPE& center, TYPE radius, unsigned idxChild) { struct CENTERARR_TYPE { POINT_TYPE child[CELL_TYPE::numChildren]; inline CENTERARR_TYPE() { CELL_TYPE::ComputeCenter(child); } }; static const CENTERARR_TYPE centers; return center + centers.child[idxChild] * radius; } // ComputeChildCenter /*----------------------------------------------------------------*/ // count the number of items contained by the given octree-cell template size_t TOctree::CELL_TYPE::GetNumItemsHeld() const { if (IsLeaf()) return GetNumItems(); size_t numItems = 0; for (int i=0; i template inline TOctree::TOctree(const ITEMARR_TYPE& items, Functor split) { Insert(items, split); } template template inline TOctree::TOctree(const ITEMARR_TYPE& items, const AABB_TYPE& aabb, Functor split) { Insert(items, aabb, split); } // constructor /*----------------------------------------------------------------*/ // destroy tree template inline void TOctree::Release() { m_indices.Release(); m_root.Release(); } // Release // swap the two octrees template inline void TOctree::Swap(TOctree& rhs) { std::swap(m_items, rhs.m_items); m_indices.Swap(rhs.m_indices); m_root.Swap(rhs.m_root); std::swap(m_radius, rhs.m_radius); } // Swap /*----------------------------------------------------------------*/ // add the given item to the tree template template void TOctree::_Insert(CELL_TYPE& cell, const POINT_TYPE& center, TYPE radius, IDX_TYPE start, IDX_TYPE size, _InsertData& insertData) { ASSERT(size > 0); // if this child cell needs to be divided further if (bForceSplit || insertData.split(size, radius)) { // init node and proceed recursively ASSERT(cell.m_child == NULL); cell.m_child = new CELL_TYPE[CELL_TYPE::numChildren]; cell.Node().center = center; struct ChildData { enum { ESTART=0, EEND=CELL_TYPE::numChildren, ESIZE=CELL_TYPE::numChildren*2, EALL=CELL_TYPE::numChildren*3}; IDX_TYPE data[EALL]; ChildData() { memset(data, 0, sizeof(IDX_TYPE)*EALL); } inline IDX_TYPE Start(unsigned i) const { return data[ESTART+i]; } inline IDX_TYPE& Start(unsigned i) { return data[ESTART+i]; } inline IDX_TYPE End(unsigned i) const { return data[EEND+i]; } inline IDX_TYPE& End(unsigned i) { return data[EEND+i]; } inline IDX_TYPE Size(unsigned i) const { return data[ESIZE+i]; } inline IDX_TYPE& Size(unsigned i) { return data[ESIZE+i]; } } childD; IDX_TYPE idx(start); for (IDX_TYPE i=0; i::NO_INDEX); const TYPE childRadius(radius / TYPE(2)); for (unsigned i=0; i::NO_INDEX; // mark the end of child successors const POINT_TYPE childCenter(CELL_TYPE::ComputeChildCenter(center, childRadius, i)); _Insert(child, childCenter, childRadius, childD.Start(i), childD.Size(i), insertData); } } else { // init leaf cell.Leaf().idxBegin = m_indices.size(); cell.Leaf().size = (SIZE_TYPE)size; for (IDX_TYPE idx=start; idx!=_InsertData::NO_INDEX; idx=insertData.successors[idx]) m_indices.push_back(idx); } } // _Insert /*----------------------------------------------------------------*/ template template inline void TOctree::Insert(const ITEMARR_TYPE& items, const AABB_TYPE& aabb, Functor split) { Release(); m_items = items.data(); // create root as node, even if we do not need to divide m_indices.Reserve(items.size()); // divide cell const POINT_TYPE center = aabb.GetCenter(); m_radius = aabb.GetSize().maxCoeff()/Type(2); // single connected list of next item indices _InsertData insertData = {items.size(), split}; std::iota(insertData.successors.begin(), insertData.successors.end(), IDX_TYPE(1)); insertData.successors.back() = _InsertData::NO_INDEX; // setup each cell _Insert(m_root, center, m_radius, 0, items.size(), insertData); } template template inline void TOctree::Insert(const ITEMARR_TYPE& items, Functor split) { ASSERT(!items.IsEmpty()); ASSERT(sizeof(POINT_TYPE) == sizeof(typename ITEMARR_TYPE::Type)); AABB_TYPE aabb((const POINT_TYPE*)items.data(), items.size()); aabb.Enlarge(ZEROTOLERANCE()*TYPE(10)); Insert(items, aabb, split); } // Insert /*----------------------------------------------------------------*/ template template void TOctree::CollectCells(const CELL_TYPE& cell, INSERTER& inserter) const { if (cell.IsLeaf()) { inserter(m_indices.data()+cell.GetFirstItemIdx(), cell.GetNumItems()); return; } for (int i=0; i template void TOctree::_ParseCells(CELL_TYPE& cell, TYPE radius, PARSER& parser) { if (cell.IsLeaf()) { parser(cell, radius); return; } const TYPE childRadius = radius / TYPE(2); for (int i=0; i template void TOctree::CollectCells(INSERTER& inserter) const { CollectCells(m_root, inserter); } // calls parser for each leaf of the octree (the CELL_TYPE operator has to be defined) template template void TOctree::ParseCells(PARSER& parser) { _ParseCells(m_root, m_radius, parser); } /*----------------------------------------------------------------*/ // find all items contained by the given bounding box template template void TOctree::_Collect(const CELL_TYPE& cell, const AABB_TYPE& aabb, INSERTER& inserter) const { if (cell.IsLeaf()) { // add all items contained by the bounding-box for (IDX_TYPE i=0; i template void TOctree::_Collect(const CELL_TYPE& cell, TYPE radius, const COLLECTOR& collector, INSERTER& inserter) const { ASSERT(!cell.IsLeaf()); const TYPE childRadius = radius / TYPE(2); for (int i=0; i template inline void TOctree::Collect(INSERTER& inserter, const AABB_TYPE& aabb) const { _Collect(m_root, aabb, inserter); } template inline void TOctree::Collect(IDXARR_TYPE& indices, const AABB_TYPE& aabb) const { IndexInserter inserter(indices); _Collect(m_root, aabb, inserter); } template template inline void TOctree::Collect(INSERTER& inserter, const POINT_TYPE& center, TYPE radius) const { _Collect(m_root, AABB_TYPE(center, radius), inserter); } template inline void TOctree::Collect(IDXARR_TYPE& indices, const POINT_TYPE& center, TYPE radius) const { IndexInserter inserter(indices); _Collect(m_root, AABB_TYPE(center, radius), inserter); } template template inline void TOctree::Collect(INSERTER& inserter, const COLLECTOR& collector) const { _Collect(m_root, m_radius, collector, inserter); } template template inline void TOctree::Collect(IDXARR_TYPE& indices, const COLLECTOR& collector) const { IndexInserter inserter(indices); _Collect(m_root, m_radius, collector, inserter); } template inline void TOctree::Collect(IDX_TYPE maxNeighbors, IDXARR_TYPE& indices, const AABB_TYPE& aabb) const { _Collect(m_root, aabb, IndexInserter(indices)); if (indices.size() > maxNeighbors) { // keep only the closest neighbors typedef TIndexScore ItemIndexScore; typedef cList ItemIndexScoreArr; ItemIndexScoreArr indexscores(indices.size()); const POINT_TYPE center(aabb.GetCenter()); FOREACH(i, indices) { const IDX_TYPE& idx = indices[i]; const TYPE score(-(center-GetItem(idx)).squaredNorm()); indexscores[i] = ItemIndexScore(idx,score); } indices.Empty(); indexscores.Sort(); for (IDX_TYPE i=0; i inline void TOctree::Collect(IDX_TYPE maxNeighbors, IDXARR_TYPE& indices, const POINT_TYPE& center, TYPE radius) const { Collect(maxNeighbors, indices, AABB_TYPE(center, radius)); } // Collect /*----------------------------------------------------------------*/ // walk through the tree and collect visible indices template template void TOctree::_Traverse(const CELL_TYPE& cell, TYPE radius, const TFrustum& frustum, INSERTER& inserter) const { ASSERT(!cell.IsLeaf()); switch (frustum.Classify(cell.GetAabb(radius))) { case CLIPPED: { const TYPE childRadius = radius / TYPE(2); for (int i=0; i template void TOctree::_TraverseCells(CELL_TYPE& cell, TYPE radius, const TFrustum& frustum, PARSER& parser) { ASSERT(!cell.IsLeaf()); switch (frustum.Classify(cell.GetAabb(radius))) { case CLIPPED: { const TYPE childRadius = radius / TYPE(2); for (int i=0; i template inline void TOctree::Traverse(const TFrustum& frustum, INSERTER& inserter) const { _Traverse(m_root, m_radius, frustum, inserter); } template template inline void TOctree::Traverse(const TFrustum& frustum, IDXARR_TYPE& indices) const { _Traverse(m_root, m_radius, frustum, IndexInserter(indices)); } template template inline void TOctree::TraverseCells(const TFrustum& frustum, PARSER& parser) { _TraverseCells(m_root, m_radius, frustum, parser); } template template inline void TOctree::TraverseCells(const TFrustum& frustum, CELLPTRARR_TYPE& leaves) { _TraverseCells(m_root, m_radius, frustum, CellInserter(leaves)); } // Traverse /*----------------------------------------------------------------*/ template template void TOctree::_SplitVolume(const CELL_TYPE& parentCell, TYPE parentRadius, unsigned idxChild, float maxArea, AREAESTIMATOR& areaEstimator, CHUNKINSERTER& chunkInserter, const UnsignedArr& indices) { ASSERT(!indices.empty()); typedef std::pair PairIndices; struct GenerateSamples { const UnsignedArr& indices; const unsigned numSamples; const unsigned halfSamples; const unsigned numCommonAxis; POINT_TYPE centers[8]; cList arrHalfIndices; GenerateSamples(const UnsignedArr& _indices) : indices(_indices), numSamples((unsigned)indices.size()), halfSamples(numSamples/2), numCommonAxis(halfSamples==4?1:2), arrHalfIndices(0, numSamples) { ASSERT(indices.size()%2 == 0 && indices.IsSorted()); ASSERT(halfSamples == 4 || halfSamples == 2); CELL_TYPE::ComputeCenter(centers); UnsignedArr samples(halfSamples); for (unsigned hs=0; hs= maxArea) ++numOverAreas; if (numOverAreas == indices.size()) { for (unsigned c: indices) if (childArea[c] > 0) _SplitVolume(cell, radius, c, maxArea, areaEstimator, chunkInserter); return; } // split mesh children and retain the components with surface smaller than the given area const cList halfIndices(std::move(GenerateSamples(indices).arrHalfIndices)); IDX bestSplit(NO_ID); float bestArea(0); Point2f bestAs; FOREACH(idx, halfIndices) { const PairIndices& pairHalfIndices = halfIndices[idx]; ASSERT(pairHalfIndices.first.size() == pairHalfIndices.second.size()); Point2f as(Point2f::ZERO); for (unsigned i=0; i qIndicesFirst(std::move(GenerateSamples(pairHalfIndices.first).arrHalfIndices)); const cList qIndicesSecond(std::move(GenerateSamples(pairHalfIndices.second).arrHalfIndices)); ASSERT(qIndicesFirst.size() == qIndicesSecond.size()); FOREACH(q, qIndicesFirst) { const PairIndices& qFirst = qIndicesFirst[q]; const PairIndices& qSecond = qIndicesSecond[q]; Eigen::Vector4f as(Eigen::Vector4f::Zero()); for (unsigned i=0; i 0) { // store found clusters for (unsigned i=0; i<4; ++i) { if (bestAs[i] < maxArea) { chunkInserter(cell, radius, bestQIndices[i]); } else { _SplitVolume(cell, radius, bestQIndices[i][0], maxArea, areaEstimator, chunkInserter); _SplitVolume(cell, radius, bestQIndices[i][1], maxArea, areaEstimator, chunkInserter); } } return; } } // split each child for (unsigned c: indices) { if (childArea[c] == 0) continue; if (childArea[c] < maxArea) chunkInserter(cell, radius, UnsignedArr{c}); else _SplitVolume(cell, radius, c, maxArea, areaEstimator, chunkInserter); } } template template void TOctree::SplitVolume(float maxArea, AREAESTIMATOR& areaEstimator, CHUNKINSERTER& chunkInserter) { CELL_TYPE parent; parent.m_child = new CELL_TYPE[1]; parent.m_child[0].m_child = m_root.m_child; parent.m_child[0].Node() = m_root.Node(); parent.Node().center = m_root.Node().center + POINT_TYPE::Constant(m_radius); _SplitVolume(parent, m_radius*TYPE(2), 0, maxArea, areaEstimator, chunkInserter); parent.m_child[0].m_child = NULL; } // SplitVolume /*----------------------------------------------------------------*/ template void TOctree::_GetDebugInfo(const CELL_TYPE& cell, unsigned nDepth, DEBUGINFO& info) const { if (cell.IsLeaf()) { if (info.minDepth > nDepth) info.minDepth = nDepth; if (info.maxDepth < nDepth) info.maxDepth = nDepth; info.avgDepth += nDepth; info.numLeaves++; return; } nDepth++; info.numNodes++; for (int i=0; i void TOctree::GetDebugInfo(DEBUGINFO* pInfo, bool bPrintStats) const { DEBUGINFO localInfo; DEBUGINFO& info = (pInfo ? *pInfo : localInfo); info.Init(); _GetDebugInfo(m_root, 0, info); info.avgDepth /= info.numLeaves; info.numItems = GetNumItems(); info.numNodes += info.numLeaves; info.memStruct = info.numNodes*sizeof(CELL_TYPE) + sizeof(TOctree); info.memItems = sizeof(IDX_TYPE)*info.numItems; info.memSize = info.memStruct + info.memItems; if (pInfo == NULL || bPrintStats) LogDebugInfo(info); } // GetDebugInfo /*----------------------------------------------------------------*/ template void TOctree::LogDebugInfo(const DEBUGINFO& info) { //VERBOSE("NoItems: %d; Mem %s; MemItems %s; MemStruct %s; AvgMemStruct %.2f%%%%; NoNodes %d; NoLeaf %d; AvgLeaf %.2f%%%%; AvgDepth %.2f; MinDepth %d; MaxDepth %d", VERBOSE("NumItems %d; Mem %s (%s items, %s struct - %.2f%%%%); NumNodes %d (leaves %d - %.2f%%%%); Depth %.2f (%d min, %d max)", info.numItems, Util::formatBytes(info.memSize).c_str(), Util::formatBytes(info.memItems).c_str(), Util::formatBytes(info.memStruct).c_str(), double(info.memStruct)*100.0/info.memSize, info.numNodes, info.numLeaves, float(info.numLeaves*100)/info.numNodes, info.avgDepth, info.minDepth, info.maxDepth); } // LogDebugInfo /*----------------------------------------------------------------*/ // if everything works fine, this function should return true template inline bool OctreeTest(unsigned iters, unsigned maxItems=1000, bool bRandom=true) { STATIC_ASSERT(DIMS > 0 && DIMS <= 3); srand(bRandom ? (unsigned)time(NULL) : 0); typedef Eigen::Matrix POINT_TYPE; typedef CLISTDEF0(POINT_TYPE) TestArr; typedef TOctree TestTree; const TYPE ptMinData[] = {0,0,0}, ptMaxData[] = {640,480,240}; typename TestTree::AABB_TYPE aabb; aabb.Set(Eigen::Map(ptMinData), Eigen::Map(ptMaxData)); aabb.Enlarge(ZEROTOLERANCE()*TYPE(10)); unsigned nTotalMatches = 0; unsigned nTotalMissed = 0; unsigned nTotalExtra = 0; #ifndef _RELEASE typename TestTree::DEBUGINFO_TYPE totalInfo; totalInfo.Init(); #endif for (unsigned iter=0; iter(RAND()%ROUND2INT(ptMaxData[j])); // random query point POINT_TYPE pt; for (int j=0; j(RAND()%ROUND2INT(ptMaxData[j])); const TYPE radius(TYPE(3+RAND()%30)); // build octree and find interest items TestTree tree(items, aabb, [](typename TestTree::IDX_TYPE size, typename TestTree::Type radius) { return size > 16 && radius > 10; }); typename TestTree::IDXARR_TYPE indices; tree.Collect(indices, pt, radius); // find interest items by brute force typename TestTree::IDXARR_TYPE trueIndices; #if 1 // use square bound typename TestTree::AABB_TYPE aabbQuery(pt, radius); FOREACH(i, items) if (aabbQuery.Intersects(items[i])) trueIndices.Insert(i); #else // use circle bound FOREACH(i, items) if ((items[i]-pt).norm() < radius) trueIndices.Insert(i); #endif // compare results unsigned nMatches = 0; FOREACH(i, trueIndices) { const typename TestTree::IDX_TYPE idx = trueIndices[i]; FOREACH(j, indices) { if (indices[j] == idx) { ++nMatches; break; } } } nTotalMatches += nMatches; nTotalMissed += (unsigned)trueIndices.size()-nMatches; nTotalExtra += (unsigned)indices.size()-nMatches; #ifndef _RELEASE // print stats typename TestTree::DEBUGINFO_TYPE info; tree.GetDebugInfo(&info); totalInfo += info; #endif } #ifndef _RELEASE TestTree::LogDebugInfo(totalInfo); VERBOSE("Test %s (TotalMissed %d, TotalExtra %d)", (nTotalMissed == 0 && nTotalExtra == 0 ? "successful" : "FAILED"), nTotalMissed, nTotalExtra); #endif return (nTotalMissed == 0 && nTotalExtra == 0); }