CloudCompare源码分析_八叉树(Octree)算法基础CC中的八叉树结构
官方參考地址:CloudCompare octree - CloudCompareWiki
CC的octree算法主要體現(xiàn)在DgmOctree.h和DgmOctree.cpp中,他采用了一種分級的結(jié)構(gòu),最大支持21級,如下,
static const int MAX_OCTREE_LEVEL = 21;然后,會(huì)事先計(jì)算得到一個(gè)分級表,
預(yù)先計(jì)算好的數(shù)據(jù)表
CC這么做的原因是,把事先能計(jì)算好的數(shù)據(jù)先存儲起來,用空間換時(shí)間的辦法,來加速運(yùn)算速度。
(1) PRE_COMPUTED_BIT_SHIFT_VALUES
//! Pre-computed bit shift values (one for each level) struct BitShiftValues {//! Default initializationBitShiftValues(){//we compute all possible valuesfor (unsigned char level = 0; level <= DgmOctree::MAX_OCTREE_LEVEL; ++level){values[level] = (3 * (DgmOctree::MAX_OCTREE_LEVEL - level));}}//! Valuesunsigned char values[DgmOctree::MAX_OCTREE_LEVEL+1]; }; static BitShiftValues PRE_COMPUTED_BIT_SHIFT_VALUES;unsigned char DgmOctree::GET_BIT_SHIFT(unsigned char level) {//return (3 * (DgmOctree::MAX_OCTREE_LEVEL - level));return PRE_COMPUTED_BIT_SHIFT_VALUES.values[level]; }所以這個(gè)value實(shí)際相當(dāng)于這樣一個(gè)表,
[0] 63 [1] 60 [2] 57 [3] 54 [4] 51 [5] 48 [6] 45 [7] 42 [8] 39 [9] 36 [10] 33 [11] 30 [12] 27 [13] 24 [14] 21 [15] 18 [16] 15 [17] 12 [18] 9 [19] 6 [20] 3 [21] 0(2) PRE_COMPUTED_POS_CODES
static const int MAX_OCTREE_LENGTH = (1 << MAX_OCTREE_LEVEL);//! Pre-computed cell codes for all potential cell positions (along a unique dimension) struct MonoDimensionalCellCodes {//! Total number of positions/values/** There are 1024 possible values at level 10, and 2M. at level 21.\warning Never pass a 'constant initializer' by reference**/static const int VALUE_COUNT = DgmOctree::MAX_OCTREE_LENGTH;//! Default initializationMonoDimensionalCellCodes(){//we compute all possible values for cell codes//(along a unique dimension, the other ones are just shifted)for (int value = 0; value < VALUE_COUNT; ++value){int mask = VALUE_COUNT;DgmOctree::CellCode code = 0;for (unsigned char k = 0; k < DgmOctree::MAX_OCTREE_LEVEL; k++){mask >>= 1;code <<= 3;if (value & mask){code |= 1;}}values[value] = code;}//we compute all possible masks as well! (all dimensions)//DgmOctree::CellCode baseMask = (1 << (3 * DgmOctree::MAX_OCTREE_LEVEL));//for (int level = DgmOctree::MAX_OCTREE_LEVEL; level >= 0; --level)//{// masks[level] = baseMask - 1;// baseMask >>= 3;//}}//! Mono-dimensional cell codesDgmOctree::CellCode values[VALUE_COUNT];//! Mono-dimensional cell masks//DgmOctree::CellCode masks[DgmOctree::MAX_OCTREE_LEVEL + 1]; }; static MonoDimensionalCellCodes PRE_COMPUTED_POS_CODES;這里,MAX_OCTREE_LENGTH == (1<<21) == 2097152 == 0x200000 ,是一個(gè)位置的極限值,這個(gè)表實(shí)際上相當(dāng)于,
[0] 0 [1] 1 [2] 8 [3] 9 [4] 64 [5] 65 [6] 72 [7] 73 [8] 512 [9] 513 [10] 520 [11] 521 [12] 576 [13] 577 [14] 584 [15] 585 [16] 4096 [17] 4097 [18] 4104 [19] 4105 [20] 4160 [21] 4161 [22] 4168 [23] 4169 [24] 4608 [25] 4609 [26] 4616 [27] 4617 [28] 4672 [29] 4673 [30] 4680 [31] 4681 ......計(jì)算基礎(chǔ)之cellCode
在設(shè)計(jì)算法之前,我們需要了解一下OcTree層級的概念。
比如我的所有點(diǎn)都在一個(gè)64x64x64的立方體內(nèi),那第0層級的邊長就是64,也就是最大的cube框的邊長,那么一個(gè)立方體包含8個(gè)cube框,下一級第1層級框的邊長大小就是64/2,依此類推,再下一級就是64/4,64/8......
另外,給一個(gè)相關(guān)講解的參考地址,
參考:數(shù)據(jù)立方體_點(diǎn)云空間數(shù)據(jù)組織——八叉樹-白紅宇的個(gè)人博客
作者對八叉樹進(jìn)行了講解,我摘錄了其中的一點(diǎn)。如下,
八叉樹將空間分割成八塊,根據(jù)2進(jìn)制, 3位2進(jìn)制即可表示8個(gè)數(shù)字,3位中的順序:zyx ,順序區(qū)分,從小遞增到大,如: 011,即z為0,x為1,y為 1的塊位置。所以 64位操作系統(tǒng)最多可分割為64/3 = 21級, 32位操作系統(tǒng)最多可分割為32/3 = 10級。
建立單維索引( CellCode)
將原有的1位2進(jìn)制轉(zhuǎn)為3位,如:001轉(zhuǎn)為000 000 001,010 轉(zhuǎn)為 000 001 000,以此類推。另外兩個(gè)維度的code分別左移1位和2位即可。
軟件初始化時(shí)就計(jì)算完了所有CellCode。
具體可參考前面講到的結(jié)構(gòu)體
static MonoDimensionalCellCodes PRE_COMPUTED_POS_CODES;
好了,前面的這個(gè)要怎么理解和使用呢?
比如,我計(jì)算得到cellPos=(2,2,2),那
PRE_COMPUTED_POS_CODES.values[2] == 8 == 000 001 000
假設(shè)對應(yīng)的層級也是21,那么,GenerateTruncatedCellCode中,返回的數(shù)據(jù)結(jié)果就是
具體可以參考DgmOctree::GenerateTruncatedCellCode函數(shù)。
也就是
000 001 000 | 000 001 000 << 1 | 000 001 000 << 2 == 000 111 000
再比如,我計(jì)算得到cellPos=(3,3,3),那
PRE_COMPUTED_POS_CODES.values[3] == 9 == 000 001 001
那么,在21級,相應(yīng)的cellcode就是
000 001 001 | 000 001 001 << 1 | 000 001 001 << 2 == 000 111 111
又比如,我計(jì)算得到cellPos=(4,4,4),那
PRE_COMPUTED_POS_CODES.values[3] == 64 == 001 000 000
那么,在21級,相應(yīng)的cellcode就是
001 000 000 | 001 000 000 << 1 | 001 000 000 << 2 == 111 000 000
看出規(guī)律了嗎?
作者的意思,就是要設(shè)計(jì)一個(gè)像c++中std::map這樣的一個(gè)結(jié)構(gòu),能夠迅速通過這個(gè)cellCode,找到任意層級的cellPos。
例如,前面在第21層級的cellPos=(4,4,4),到了第20層級,就要右移3位,因?yàn)?/p>
GET_BIT_SHIFT(20) == 3
不難判斷,這個(gè)cellCode對應(yīng)的cellPos正是(2,2,2)。
好了,有了這些相關(guān)基礎(chǔ)知識之后, 我們開始講解點(diǎn)云的八叉樹算法的架構(gòu)與原理。
ccPointCloud : ccGenericPointCloud
class QCC_DB_LIB_API ccPointCloud : public CCCoreLib::PointCloudTpl<ccGenericPointCloud, QString> { }這里,ccGenericPointCloud的父類是GenericIndexedCloudPersist
class QCC_DB_LIB_API ccGenericPointCloud : public ccShiftedObject, public CCCoreLib::GenericIndexedCloudPersist {friend class ccMesh;這是一個(gè)純虛類,GenericIndexedCloudPersist,其所有的上級父類也都是純虛類,或者說是接口,包括GenericIndexedCloud,GenericIndexedCloud, 以及GenericCloud,
class CC_CORE_LIB_API GenericIndexedCloudPersist : virtual public GenericIndexedCloud {...... }class CC_CORE_LIB_API GenericIndexedCloud : virtual public GenericCloud {...... }class CC_CORE_LIB_API GenericCloud{...... }計(jì)算過程(程序結(jié)構(gòu))
當(dāng)在CloudCompare中按下Edit-->Octree-->Compute之后,就會(huì)啟動(dòng)ocTree的計(jì)算,
void MainWindow::doActionComputeOctree() {if ( !ccEntityAction::computeOctree(m_selectedEntities, this) )return;refreshAll();updateUI(); }computeOctree會(huì)對所有選中的實(shí)際進(jìn)行計(jì)算,在該函數(shù)中,先是得到entity的點(diǎn)云clouds,然后逐個(gè)對其進(jìn)行計(jì)算,octree = cloud->computeOctree(..),或者直接生成octree = ccOctree::Shared(new ccOctree(cloud)),
bool computeOctree(const ccHObject::Container &selectedEntities, QWidget* parent) {for (ccHObject* ent : selectedEntities){......ccGenericPointCloud* cloud = ccHObjectCaster::ToGenericPointCloud(ent, &lockedVertices);......clouds.insert(cloud);......}.......for (const auto cloud : clouds){......switch (coDlg.getMode()){case ccComputeOctreeDlg::DEFAULT:octree = cloud->computeOctree(&pDlg);break;case ccComputeOctreeDlg::MIN_CELL_SIZE:case ccComputeOctreeDlg::CUSTOM_BBOX:{......octree = ccOctree::Shared(new ccOctree(cloud));......}break;}}...... }接下,調(diào)用ccGenericPointCloud::computeOctree,
ccOctree::Shared ccGenericPointCloud::computeOctree(CCCoreLib::GenericProgressCallback* progressCb, bool autoAddChild/*=true*/) {deleteOctree();ccOctree::Shared octree = ccOctree::Shared(new ccOctree(this));if (octree->build(progressCb) > 0){setOctree(octree, autoAddChild);}else{octree.clear();}return octree; }在這里面,會(huì)先new出一個(gè)ccOcTree,并把this也就是ccGenericPointCloud傳入進(jìn)去給m_theAssociatedCloud,然后通過build進(jìn)行octree的計(jì)算,
int DgmOctree::build(GenericProgressCallback* progressCb) {if (!m_theAssociatedCloud){assert(false);return -1;}if (!m_thePointsAndTheirCellCodes.empty()){clear();}m_theAssociatedCloud->getBoundingBox(m_pointsMin, m_pointsMax);m_dimMin = m_pointsMin;m_dimMax = m_pointsMax;//we make this bounding-box cubical (+0.1% growth to avoid round-off issues when projecting points in the octree)CCMiscTools::MakeMinAndMaxCubical(m_dimMin, m_dimMax, 0.001);return genericBuild(progressCb); }這里要注意,ccOctree的父類正是DgmOctree,
class QCC_DB_LIB_API ccOctree : public QObject, public CCCoreLib::DgmOctree計(jì)算OcTree
結(jié)構(gòu)體IndexAndCode中,給出的是點(diǎn)的index和所在的cube的code,大體上是這樣定義的,
struct IndexAndCode{//! indexunsigned theIndex;//! cell codeCellCode theCode;......}using cellsContainer = std::vector<IndexAndCode>;//! The coded octree structurecellsContainer m_thePointsAndTheirCellCodes;接下來我們看一下DgmOctree::genericBuild函數(shù)的主體,
int DgmOctree::genericBuild(GenericProgressCallback* progressCb) {......try{m_thePointsAndTheirCellCodes.resize(pointCount); }......updateCellSizeTable();//for all pointscellsContainer::iterator it = m_thePointsAndTheirCellCodes.begin();for (unsigned i = 0; i < pointCount; i++){const CCVector3* P = m_theAssociatedCloud->getPoint(i);//does the point falls in the 'accepted points' box?//(potentially different from the octree box - see DgmOctree::build)if ( (P->x >= m_pointsMin[0]) && (P->x <= m_pointsMax[0])&& (P->y >= m_pointsMin[1]) && (P->y <= m_pointsMax[1])&& (P->z >= m_pointsMin[2]) && (P->z <= m_pointsMax[2]) ){//compute the position of the cell that includes this pointTuple3i cellPos;getTheCellPosWhichIncludesThePoint(P, cellPos);......it->theIndex = i;it->theCode = GenerateTruncatedCellCode(cellPos, MAX_OCTREE_LEVEL);......++it;++m_numberOfProjectedPoints;}......//we sort the 'cells' by ascending code orderParallelSort(m_thePointsAndTheirCellCodes.begin(), m_thePointsAndTheirCellCodes.end(), IndexAndCode::codeComp);//update the pre-computed 'number of cells per level of subdivision' arrayupdateCellCountTable();...... }下面我們來看這個(gè)函數(shù)是如何計(jì)算octree的,
(1) 函數(shù)中先是通過m_thePointsAndTheirCellCodes.resize分配內(nèi)存,
m_thePointsAndTheirCellCodes.resize(pointCount);(2)然后,通過updateCellSizeTable不斷分割尺寸,得到每一級cube框的大小,例如,假設(shè)最大的框的邊長為64,那么一個(gè)立方體包含8個(gè)cube框,下一級框的邊長大小就是64/2,依此類推,再下一級就是64/4,64/8......
void DgmOctree::updateCellSizeTable() {//update the cell dimension for each subdivision levelm_cellSize[0] = m_dimMax.x - m_dimMin.x;unsigned long long d = 1;for (int k = 1; k <= MAX_OCTREE_LEVEL; k++){d <<= 1;m_cellSize[k] = m_cellSize[0] / d;} }然后,函數(shù)開始逐個(gè)點(diǎn)逐個(gè)點(diǎn)地計(jì)算。
(3) 通過getTheCellPosWhichIncludesThePoint計(jì)算點(diǎn)的cellPos。這里的cellPos是指最小的cube的序號,和前面的講解一樣,假設(shè)0級cell大小是64,那么第20級的cell大小就是cs = 64/2^20 = 0.00006103515625,那么在某個(gè)維度上其序號就是(x-xmin)/cs,如下,
//! Type of the coordinates of a (N-D) point using PointCoordinateType = float;//! Returns the position FOR THE DEEPEST LEVEL OF SUBDIVISION of the cell that includes a given point/** The cell coordinates can be negative or greater than 2^MAX_OCTREE_LEVEL-1as the point can lie outside the octree bounding-box.\param thePoint the query point\param cellPos the computed position**/ inline void getTheCellPosWhichIncludesThePoint(const CCVector3* thePoint, Tuple3i& cellPos) const {const PointCoordinateType& cs = getCellSize(MAX_OCTREE_LEVEL);//DGM: if we admit that cs >= 0, then the 'floor' operator is useless (int cast = truncation)cellPos.x = static_cast<int>(/*floor*/(thePoint->x - m_dimMin.x) / cs);cellPos.y = static_cast<int>(/*floor*/(thePoint->y - m_dimMin.y) / cs);cellPos.z = static_cast<int>(/*floor*/(thePoint->z - m_dimMin.z) / cs); }(4) 獲取點(diǎn)的cellCode,
這個(gè),是我們在基礎(chǔ)部分講過的函數(shù)DgmOctree::GenerateTruncatedCellCode,
DgmOctree::CellCode DgmOctree::GenerateTruncatedCellCode(const Tuple3i& cellPos, unsigned char level) {assert(cellPos.x >= 0 && cellPos.x < MonoDimensionalCellCodes::VALUE_COUNT&&cellPos.y >= 0 && cellPos.y < MonoDimensionalCellCodes::VALUE_COUNT&&cellPos.z >= 0 && cellPos.z < MonoDimensionalCellCodes::VALUE_COUNT);const unsigned char shift = MAX_OCTREE_LEVEL - level;return(PRE_COMPUTED_POS_CODES.values[cellPos.x << shift]|(PRE_COMPUTED_POS_CODES.values[cellPos.y << shift] << 1)|(PRE_COMPUTED_POS_CODES.values[cellPos.z << shift] << 2)) >> GET_BIT_SHIFT(level); }(5)按cellCode大小排序
ParallelSort(m_thePointsAndTheirCellCodes.begin(), m_thePointsAndTheirCellCodes.end(), IndexAndCode::codeComp);值得說明的是,在函數(shù)中,對變量m_fillIndexes也進(jìn)行了計(jì)算,因?yàn)樵创a比較簡單,一目了然,這里就不展開講了。
本文結(jié)束。
總結(jié)
以上是生活随笔為你收集整理的CloudCompare源码分析_八叉树(Octree)算法基础CC中的八叉树结构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机操作系统分页试题,计算机操作系统典
- 下一篇: 流星灯C语言程序,(18)改进led驱动