八叉樹
維基釋義:八叉樹(Octree)是一種用於描述三維空間的樹狀數據結構。八叉樹的每個節點表示一個正方體的體積元素,每個節點有八個子節點,
這八個子節點所表示的體積元素加在一起就等於父節點的體積。一般中心點作為節點的分叉中心。
百度百科釋義:八叉樹(Octree)的定義是:若不為空樹的話,樹中任一節點的子節點恰好只會有八個,或零個,也就是子節點不會有0與8以外的數目。
那么,這要用來做什么?想象一個立方體, 我們最少可以切成多少個相同等分的小立方體?答案就是8個。再想象我們有一個房間,房間里某個角落藏着一
枚金幣,我們想很快的把金幣找出來,聰明的你會怎 么做?我們可以把房間當成一個立方體,先切成八個小立方體,然后排除掉沒有放任何東西的小立方體,再把有可能藏金幣的小立方體繼續切八等份….如此下去, 平均在Log8(房間內的所有物品數)的時間內就可找到金幣。因此,八叉樹就是用在3D空間中的場景管理,可以很快地知道物體在3D場景中的位置,或偵測 與其它物體是否有碰撞以及是否在可視范圍內。
八叉樹是一種用於描述三維空間的樹狀數據結構。八叉樹的每個節點表示一個正方體的體積元素,每個節點有八個子節點,將八個子節點所表示的體積元素加在一起就等於父節點的體積。
實現八叉樹的原理
(1). 設定最大遞歸深度。
(2). 找出場景的最大尺寸,並以此尺寸建立第一個立方體。
(3). 依序將單位元元素丟入能被包含且沒有子節點的立方體。
(4). 若沒達到最大遞歸深度,就進行細分八等份,再將該立方體所裝的單位元元素全部分擔給八個子立方體。
(5). 若發現子立方體所分配到的單位元元素數量不為零且跟父立方體是一樣的,則該子立方體停止細分,因為跟據空間分割理論,
細分的空間所得到的分配必定較少,若是一樣數目,則再怎么切數目還是一樣,會造成無窮切割的情形。
(6). 重復3,直到達到最大遞歸深度。
八叉樹三維數據結構
(一) 基本原理
用八叉樹來表示三維形體,並研究在這種表示下的各種操作及應用是在進入80年代后才比較全面地開展起來的。這種方法,既可以看成是四叉樹方法在三維空間的推
廣,也可以認為是用三維體素陣列表示形體方法的一種改進。
八叉樹的邏輯結構如下:
假設要表示的形體V可以放在一個充分大的正方體C內,C的邊長為2n,形體V=C,它的八叉樹可以用以下的遞歸方法來定義:
八 叉樹的每個節點與C的一個子立方體對應,樹根與C本身相對應,如果V=C,那么V的八叉樹僅有樹根,如果V≠C,則將C等分為八個子立方體,每個子立方體
與樹根的一個子節點相對應。只要某個子立方體不是完全空白或完全為V所占據,就要被八等分(圖2-5-1),從而對應的節點也就有了八個子節點。這樣的遞
歸判斷、分割一直要進行到節點所對應的立方體或是完全空白,或是完全為V占據,或是其大小已是預先定義的體素大小,並且對它與V之交作一定的“舍入”,使
體素或認為是空白的,或認為是V占據的。
如此所生成的八叉樹上的節點可分為三類:
灰節點,它對應的立方體部分地為V所占據;
白節點,它所對應的立方體中無V的內容;
黑節點,它所對應的立方體全為V所占據。
后兩類又稱為葉結點。形體V關於C的八叉樹的邏輯結構是這樣的:它是一顆樹,其上的節點要么是葉節點,要么就是有八個子節點的灰節點。根節點與C相對應,
其它節點與C的某個子立方體相對應。
因為八叉樹的結構與四叉樹的結構是如此的相似,所以八叉樹的存貯結構方式可以完全沿用四叉樹的有關方法。因而,根據不同的存貯方式,八叉樹也可以分別稱為
常規的、線性的、一對八的八叉樹等等。
另外,由於這種方法充分利用了形體在空上的相關性,因此,一般來說,它所占用的存貯空間要比三維體素陣列的少。但是實際上它還是使用了相當多的存貯,這並 不是八叉樹的主要優點。這一方法的主要優點在於可以非常方便地實現有廣泛用途的集合運算(例如可以求兩個物體的並、交、差等運算),而這些恰是其它表示方 法比較難以處理或者需要耗費許多計算資源的地方。不僅如此,由於這種方法的有序性及分層性,因而對顯示精度和速度的平衡、隱線和隱面的消除等,帶來了很大 的方便,特別有用。
(二)八叉樹的存貯結構
八叉樹有三種不同的存貯結構,分別是規則方式、線性方式以及一對八方式。相應的八叉樹也分別稱為規則八叉樹、線性八叉樹以及一對八式八叉樹。不同的存貯結構
的空間利用率及運算操作的方便性是不同的。分析表明,一對八式八叉樹優點更多一些。
1、規則八叉樹
規則八叉樹的存貯結構用一個有九個字段的記錄來表示樹中的每個結點。其中一個字段用來描述該結點的特性(在目前假定下,只要描述它是灰、白、黑三類結點中 哪一類即可),其余的八個字段用來作為存放指向其八個子結點的指針。這是最普遍使用的表示樹形數據的存貯結構方式。
規則八叉樹缺陷較多,最大的問題是指針占用了大量的空間。假定每個指針要用兩個字節表示,而結點的描述用一個字節,那么存放指針要占總的存貯量的94%。
因此,這種方法雖然十分自然,容易掌握,但在存貯空間的使用率方面不很理想。
2、線性八叉樹
線性八叉樹注重考慮如何提高空間利用率。用某一預先確定的次序遍歷八叉樹(例如以深度第一的方式),將八叉樹轉換成一個線性表(圖2-5-2),表的每個 元素與一個結點相對應。對於結點的描述可以豐富一點,例如用適當的方式來說明它是否為葉結點,如果不是葉結點時還可用其八個子結點值的平均值作為非葉結點 的值等等。這樣,可以在內存中以緊湊的方式來表示線性表,可以不用指針或者僅用一個指針表示即可。
3、一對八式的八叉樹
一個非葉結點有八個子結點,為了確定起見,將它們分別標記為0,1,2,3,4,5,6,7。從上面的介紹可以看到,如果一個記錄與一個結點相對應,那么 在這個記錄中描述的是這個結點的八個子結點的特性值。而指針給出的則是該八個子結點所對應記錄的存放處,而且還隱含地假定了這些子結點記錄存放的次序。也 就是說,即使某個記錄是不必要的(例如,該結點已是葉結點),那么相應的存貯位置也必須空閑在那里(圖2-5-3),以保證不會錯誤地存取到其它同輩結點 的記錄。這樣當然會有一定的浪費,除非它是完全的八叉樹,即所有的葉結點均在同一層次出現,而在該層次之上的所有層中的結點均為非葉結點。
為了克服這種缺陷,有兩條途徑可以采納。一是增加計算量,在記錄中增加一定的信息,使計算工作適當減少或者更方便。
(轉自:http://blog.csdn.net/augusdi/article/details/36001543)
程序如下(稍作修改)
主要是把八叉樹單獨提出來,作為一個文件otctree.h,然后方便引入主程序處理
1 #pragma once; 2 #ifndef __OCTRESSNODE__ 3 #define __OCTRESSNODE__ 4 5 #include <iostream> 6 7 template<class T> 8 struct OctreeNode 9 { 10 T data; //節點數據 11 T xmin,xmax; //節點坐標,即六面體個頂點的坐標 12 T ymin,ymax; 13 T zmin,zmax; 14 OctreeNode <T>*top_left_front,*top_left_back; //該節點的個子結點 15 OctreeNode <T>*top_right_front,*top_right_back; 16 OctreeNode <T>*bottom_left_front,*bottom_left_back; 17 OctreeNode <T>*bottom_right_front,*bottom_right_back; 18 //節點類 19 OctreeNode (T nodeValue = T(), 20 T xminValue = T(),T xmaxValue = T(), 21 T yminValue = T(),T ymaxValue = T(), 22 T zminValue = T(),T zmaxValue = T(), 23 OctreeNode<T>*top_left_front_Node = NULL, 24 OctreeNode<T>*top_left_back_Node = NULL, 25 OctreeNode<T>*top_right_front_Node = NULL, 26 OctreeNode<T>*top_right_back_Node = NULL, 27 OctreeNode<T>*bottom_left_front_Node = NULL, 28 OctreeNode<T>*bottom_left_back_Node = NULL, 29 OctreeNode<T>*bottom_right_front_Node = NULL, 30 OctreeNode<T>*bottom_right_back_Node = NULL ) 31 :data(nodeValue), 32 xmin(xminValue),xmax(xmaxValue), 33 ymin(yminValue),ymax(ymaxValue), 34 zmin(zminValue),zmax(zmaxValue), 35 top_left_front(top_left_front_Node), 36 top_left_back(top_left_back_Node), 37 top_right_front(top_right_front_Node), 38 top_right_back(top_right_back_Node), 39 bottom_left_front(bottom_left_front_Node), 40 bottom_left_back(bottom_left_back_Node), 41 bottom_right_front(bottom_right_front_Node), 42 bottom_right_back(bottom_right_back_Node){ } 43 }; 44 //創建八叉樹 45 template <class T> 46 inline void createOctree(OctreeNode<T> * &root,int maxdepth,double xmin,double xmax,double ymin,double ymax,double zmin,double zmax) 47 { 48 cout<<"處理中,請稍候……"<<endl; 49 maxdepth=maxdepth-1; //每遞歸一次就將最大遞歸深度-1 50 if(maxdepth>=0)// 51 { 52 root=new OctreeNode<T>(); 53 //cout<<"請輸入節點值:"; 54 root->data =9;//為節點賦值,可以存儲節點信息,如物體可見性。由於是簡單實現八叉樹功能,簡單賦值為9。 55 //cin>>root->data; //為節點賦值 56 root->xmin=xmin; //為節點坐標賦值 57 root->xmax=xmax; 58 root->ymin=ymin; 59 root->ymax=ymax; 60 root->zmin=zmin; 61 root->zmax=zmax; 62 double xm=(xmax-xmin)/2;//計算節點每個維度上的半邊長 63 double ym=(ymax-ymin)/2; 64 double zm=(ymax-ymin)/2; 65 //遞歸創建子樹,根據每一個節點所處(是幾號節點)的位置決定其子結點的坐標。 66 createOctree(root->top_left_front,maxdepth,xmin,xmax-xm,ymax-ym,ymax,zmax-zm,zmax); 67 createOctree(root->top_left_back,maxdepth,xmin,xmax-xm,ymin,ymax-ym,zmax-zm,zmax); 68 createOctree(root->top_right_front,maxdepth,xmax-xm,xmax,ymax-ym,ymax,zmax-zm,zmax); 69 createOctree(root->top_right_back,maxdepth,xmax-xm,xmax,ymin,ymax-ym,zmax-zm,zmax); 70 createOctree(root->bottom_left_front,maxdepth,xmin,xmax-xm,ymax-ym,ymax,zmin,zmax-zm); 71 createOctree(root->bottom_left_back,maxdepth,xmin,xmax-xm,ymin,ymax-ym,zmin,zmax-zm); 72 createOctree(root->bottom_right_front,maxdepth,xmax-xm,xmax,ymax-ym,ymax,zmin,zmax-zm); 73 createOctree(root->bottom_right_back,maxdepth,xmax-xm,xmax,ymin,ymax-ym,zmin,zmax-zm); 74 } else 75 cout << "深度< 0" << endl; 76 } 77 int i=1; 78 //先序遍歷八叉樹 79 template <class T> 80 inline void preOrder( OctreeNode<T> * & p) 81 { 82 if(p) 83 { 84 cout<<i<<".當前節點的值為:"<<p->data<<"\n坐標為:"; 85 cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; 86 cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; 87 cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; 88 i+=1; 89 cout<<endl; 90 preOrder(p->top_left_front); 91 preOrder(p->top_left_back); 92 preOrder(p->top_right_front); 93 preOrder(p->top_right_back); 94 preOrder(p->bottom_left_front); 95 preOrder(p->bottom_left_back); 96 preOrder(p->bottom_right_front); 97 preOrder(p->bottom_right_back); 98 cout<<endl; 99 } 100 } 101 //求八叉樹的深度 102 template<class T> 103 inline int depth(OctreeNode<T> *& p) 104 { 105 if(p == NULL) 106 return -1; 107 int h =depth(p->top_left_front); 108 return h+1; 109 } 110 //計算單位長度,為查找點做准備 111 //計算輸入(n-1)個2相乘 112 inline int cal(int num) 113 { 114 int result=1; 115 if(1==num) 116 result=1; 117 else 118 { 119 for(int i=1;i<num;i++) 120 result=2*result; 121 } 122 return result; 123 } 124 //查找點 125 int maxdepth=0; 126 int times=0; 127 static double xmin=0,xmax=0,ymin=0,ymax=0,zmin=0,zmax=0; 128 int tmaxdepth=0; 129 double txm=1,tym=1,tzm=1;//計算 detX/(遞歸深度的步長) 130 template<class T> 131 inline void find(OctreeNode<T> *& p,double x,double y,double z) 132 { 133 double xm=(p->xmax-p->xmin)/2; 134 double ym=(p->ymax-p->ymin)/2; 135 double zm=(p->ymax-p->ymin)/2; 136 times++; 137 if(x>xmax || x<xmin|| y>ymax || y<ymin || z>zmax || z<zmin) 138 { 139 cout<<"該點不在場景中!"<<endl; 140 return; 141 } 142 if(x<=p->xmin+txm&& x>=p->xmax-txm && y<=p->ymin+tym &&y>=p->ymax-tym && z<=p->zmin+tzm &&z>=p->zmax-tzm ) 143 { 144 cout<<endl<<"找到該點!"<<"該點位於"<<endl; 145 cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; 146 cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; 147 cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; 148 cout<<"節點內!"<<endl; 149 cout<<"共經過"<<times<<"次遞歸!"<<endl; 150 } 151 else if(x<(p->xmax-xm) && y<(p->ymax-ym) &&z<(p->zmax-zm)) 152 { 153 cout<<"當前經過節點坐標:"<<endl; 154 cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; 155 cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; 156 cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; 157 cout<<endl; 158 find(p->bottom_left_back,x,y,z); 159 } 160 else if(x<(p->xmax-xm) && y<(p->ymax-ym) &&z>(p->zmax-zm)) 161 { 162 cout<<"當前經過節點坐標:"<<endl; 163 cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; 164 cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; 165 cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; 166 cout<<endl; 167 find(p->top_left_back,x,y,z); 168 } 169 else if(x>(p->xmax-xm) && y<(p->ymax-ym) &&z<(p->zmax-zm)) 170 { 171 cout<<"當前經過節點坐標:"<<endl; 172 cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; 173 cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; 174 cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; 175 cout<<endl; 176 find(p->bottom_right_back,x,y,z); 177 } 178 else if(x>(p->xmax-xm) && y<(p->ymax-ym) &&z>(p->zmax-zm)) 179 { 180 cout<<"當前經過節點坐標:"<<endl; 181 cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; 182 cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; 183 cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; 184 cout<<endl; 185 find(p->top_right_back,x,y,z); 186 } 187 else if(x<(p->xmax-xm) && y>(p->ymax-ym) &&z<(p->zmax-zm)) 188 { 189 cout<<"當前經過節點坐標:"<<endl; 190 cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; 191 cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; 192 cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; 193 cout<<endl; 194 find(p->bottom_left_front,x,y,z); 195 } 196 else if(x<(p->xmax-xm) && y>(p->ymax-ym) &&z>(p->zmax-zm)) 197 { 198 cout<<"當前經過節點坐標:"<<endl; 199 cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; 200 cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; 201 cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; 202 cout<<endl; 203 find(p->top_left_front,x,y,z); 204 } 205 else if(x>(p->xmax-xm) && y>(p->ymax-ym) &&z<(p->zmax-zm)) 206 { 207 cout<<"當前經過節點坐標:"<<endl; 208 cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; 209 cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; 210 cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; 211 cout<<endl; 212 find(p->bottom_right_front,x,y,z); 213 } 214 else if(x>(p->xmax-xm) && y>(p->ymax-ym) &&z>(p->zmax-zm)) 215 { 216 cout<<"當前經過節點坐標:"<<endl; 217 cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; 218 cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; 219 cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; 220 cout<<endl; 221 find(p->top_right_front,x,y,z); 222 } 223 } 224 //main函數 225 /** 226 int main () 227 { 228 OctreeNode<double> *rootNode = NULL; 229 int choiced = 0; 230 while(true) 231 { 232 system("cls"); 233 cout<<"請選擇操作:\n"; 234 cout<<"1.創建八叉樹 2.先序遍歷八叉樹\n"; 235 cout<<"3.查看樹深度 4.查找節點 \n"; 236 cout<<"0.退出\n\n"; 237 cin>>choiced; 238 if(choiced == 0) 239 return 0; 240 else if(choiced == 1) 241 { 242 system("cls"); 243 cout<<"請輸入最大遞歸深度:"<<endl; 244 cin>>maxdepth; 245 cout<<"請輸入外包盒坐標,順序如下:xmin,xmax,ymin,ymax,zmin,zmax"<<endl; 246 cin>>xmin>>xmax>>ymin>>ymax>>zmin>>zmax; 247 if(maxdepth>=0|| xmax>xmin || ymax>ymin || zmax>zmin || xmin>0 || ymin>0||zmin>0) 248 { 249 tmaxdepth=cal(maxdepth); 250 txm=(xmax-xmin)/tmaxdepth; 251 tym=(ymax-ymin)/tmaxdepth; 252 tzm=(zmax-zmin)/tmaxdepth; 253 createOctree(rootNode,maxdepth,xmin,xmax,ymin,ymax,zmin,zmax); 254 } 255 else 256 { 257 cout<<"輸入錯誤!"; 258 return 0; 259 } 260 } 261 else if(choiced == 2) 262 { 263 system("cls"); 264 cout<<"先序遍歷八叉樹結果:/n"; 265 i=1; 266 preOrder(rootNode); 267 cout<<endl; 268 system("pause"); 269 } 270 else if(choiced == 3) 271 { 272 system("cls"); 273 int dep =depth(rootNode); 274 cout<<"此八叉樹的深度為"<<dep+1<<endl; 275 system("pause"); 276 } 277 else if(choiced == 4) 278 { 279 system("cls"); 280 cout<<"請輸入您希望查找的點的坐標,順序如下:x,y,z\n"; 281 double x,y,z; 282 cin>>x>>y>>z; 283 times=0; 284 cout<<endl<<"開始搜尋該點……"<<endl; 285 find(rootNode,x,y,z); 286 system("pause"); 287 } 288 else 289 { 290 system("cls"); 291 cout<<"\n\n錯誤選擇!\n"; 292 system("pause"); 293 } 294 } 295 } 296 */ 297 #endif
主程序調用如下
1 #include "stdafx.h" 2 #include "octortrees.h" 3 #include <string> 4 using namespace std; 5 int _tmain(int argc, _TCHAR* argv[]) 6 { 7 OctreeNode<double> *rootNode = NULL; 8 int choiced = 0; 9 while(true) 10 { 11 system("cls"); 12 cout<<"請選擇操作:\n"; 13 cout<<"1.創建八叉樹 2.先序遍歷八叉樹\n"; 14 cout<<"3.查看樹深度 4.查找節點 \n"; 15 cout<<"0.退出\n\n"; 16 cin>>choiced; 17 if(choiced == 0) 18 return 0; 19 else if(choiced == 1) 20 { 21 system("cls"); 22 cout<<"請輸入最大遞歸深度:"<<endl; 23 cin>>maxdepth;//遞歸深度 24 cout<<"請輸入外包盒坐標,順序如下:xmin,xmax,ymin,ymax,zmin,zmax"<<endl; 25 cin>>xmin>>xmax>>ymin>>ymax>>zmin>>zmax; 26 if(maxdepth>=0|| xmax>xmin || ymax>ymin || zmax>zmin || xmin>0 || ymin>0||zmin>0) 27 { 28 tmaxdepth=cal(maxdepth); 29 txm=(xmax-xmin)/tmaxdepth; 30 tym=(ymax-ymin)/tmaxdepth; 31 tzm=(zmax-zmin)/tmaxdepth; 32 createOctree(rootNode,maxdepth,xmin,xmax,ymin,ymax,zmin,zmax); 33 34 cout << "8叉樹創建完畢, input the 'continue' to \n" << endl;41 } 42 else 43 { 44 cout<<"輸入錯誤!"; 45 return 0; 46 } 47 } 48 else if(choiced == 2) 49 { 50 system("cls"); 51 cout<<"先序遍歷八叉樹結果:/n"; 52 i=1; 53 preOrder(rootNode); 54 cout<<endl; 55 system("pause"); 56 } 57 else if(choiced == 3) 58 { 59 system("cls"); 60 int dep =depth(rootNode); 61 cout<<"此八叉樹的深度為"<<dep+1<<endl; 62 system("pause"); 63 } 64 else if(choiced == 4) 65 { 66 system("cls"); 67 cout<<"請輸入您希望查找的點的坐標,順序如下:x,y,z\n"; 68 double x,y,z; 69 cin>>x>>y>>z; 70 times=0; 71 cout<<endl<<"開始搜尋該點……"<<endl; 72 find(rootNode,x,y,z); 73 system("pause"); 74 } 75 else 76 { 77 system("cls"); 78 cout<<"\n\n錯誤選擇!\n"; 79 system("pause"); 80 } 81 } 82 83 cin.get(); 84 return 0; 85 }