八叉樹Octree
八叉樹
維基釋義:八叉樹(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),以保證不會錯誤地存取到其它同輩結點的記錄。這樣當然會有一定的浪費,除非它是完全的八叉樹,即所有的葉結點均在同一層次出現,而在該層次之上的所有層中的結點均為非葉結點。
為了克服這種缺陷,有兩條途徑可以採納。一是增加計算量,在記錄中增加一定的資訊,使計算工作適當減少或者更方便。
此上轉載處:
實現八叉樹的原理
八叉樹要用來做什麼?想象一個立方體,我們最少可以切成多少個相同等分的小立方體?答案就是8個。再想象我們有一個房間,房間裡某個角落藏著一枚金幣,我們想很快的把金幣找出來,聰明的你會怎麼做?我們可以把房間當成一個立方體,先切成八個小立方體,然後排除掉沒有放任何東西的小立方體,再把有可能藏金幣的小立方體繼續切八等份….如此下去,平均在Log8(房間內的所有物品數)的時間內就可找到金幣。因此,八叉樹就是用在3D空間中的場景管理,可以很快地知道物體在3D場景中的位置,或偵測與其它物體是否有碰撞以及是否在可視範圍內。
(1). 設定最大遞迴深度(2). 找出場景的最大尺寸,並以此尺寸建立第一個立方體(3). 依序將單位元元素丟入能被包含且沒有子節點的立方體(4). 若沒有達到最大遞迴深度,就進行細分八等份,再將該立方體所裝的單位元元素全部分擔給八個子立方體(5). 若發現子立方體所分配到的單位元元素數量不為零且跟父立方體是一樣的,則該子立方體停止細分,因為跟據空間分割理論,細分的空間所得到的分配必定較少,若是一樣數目,則再怎麼切數目還是一樣,會造成無窮切割的情形。(6). 重複3,直到達到最大遞迴深度。
具體程式碼:
#include <iostream> using namespace std; //定義八叉樹節點類 template<class T> struct OctreeNode { T data; //節點資料 T xmin,xmax; //節點座標,即六面體個頂點的座標 T ymin,ymax; T zmin,zmax; OctreeNode <T>*top_left_front,*top_left_back; //該節點的個子結點 OctreeNode <T>*top_right_front,*top_right_back; OctreeNode <T>*bottom_left_front,*bottom_left_back; OctreeNode <T>*bottom_right_front,*bottom_right_back; OctreeNode //節點類 (T nodeValue = T(), T xminValue = T(),T xmaxValue = T(), T yminValue = T(),T ymaxValue = T(), T zminValue = T(),T zmaxValue = T(), OctreeNode<T>*top_left_front_Node = NULL, OctreeNode<T>*top_left_back_Node = NULL, OctreeNode<T>*top_right_front_Node = NULL, OctreeNode<T>*top_right_back_Node = NULL, OctreeNode<T>*bottom_left_front_Node = NULL, OctreeNode<T>*bottom_left_back_Node = NULL, OctreeNode<T>*bottom_right_front_Node = NULL, OctreeNode<T>*bottom_right_back_Node = NULL ) :data(nodeValue), xmin(xminValue),xmax(xmaxValue), ymin(yminValue),ymax(ymaxValue), zmin(zminValue),zmax(zmaxValue), top_left_front(top_left_front_Node), top_left_back(top_left_back_Node), top_right_front(top_right_front_Node), top_right_back(top_right_back_Node), bottom_left_front(bottom_left_front_Node), bottom_left_back(bottom_left_back_Node), bottom_right_front(bottom_right_front_Node), bottom_right_back(bottom_right_back_Node){} }; //建立八叉樹 template <class T> void createOctree(OctreeNode<T> * &root,int maxdepth,double xmin,double xmax,double ymin,double ymax,double zmin,double zmax) { //cout<<"處理中,請稍候……"<<endl; maxdepth=maxdepth-1; //每遞迴一次就將最大遞迴深度-1 if(maxdepth>=0) { root=new OctreeNode<T>(); cout<<"請輸入節點值:"; //root->data =9;//為節點賦值,可以儲存節點資訊,如物體可見性。由於是簡單實現八叉樹功能,簡單賦值為9。 cin>>root->data; //為節點賦值 root->xmin=xmin; //為節點座標賦值 root->xmax=xmax; root->ymin=ymin; root->ymax=ymax; root->zmin=zmin; root->zmax=zmax; double xm=(xmax-xmin)/2;//計算節點個維度上的半邊長 double ym=(ymax-ymin)/2; double zm=(ymax-ymin)/2; //遞迴建立子樹,根據每一個節點所處(是幾號節點)的位置決定其子結點的座標。 createOctree(root->top_left_front,maxdepth,xmin,xmax-xm,ymax-ym,ymax,zmax-zm,zmax); createOctree(root->top_left_back,maxdepth,xmin,xmax-xm,ymin,ymax-ym,zmax-zm,zmax); createOctree(root->top_right_front,maxdepth,xmax-xm,xmax,ymax-ym,ymax,zmax-zm,zmax); createOctree(root->top_right_back,maxdepth,xmax-xm,xmax,ymin,ymax-ym,zmax-zm,zmax); createOctree(root->bottom_left_front,maxdepth,xmin,xmax-xm,ymax-ym,ymax,zmin,zmax-zm); createOctree(root->bottom_left_back,maxdepth,xmin,xmax-xm,ymin,ymax-ym,zmin,zmax-zm); createOctree(root->bottom_right_front,maxdepth,xmax-xm,xmax,ymax-ym,ymax,zmin,zmax-zm); createOctree(root->bottom_right_back,maxdepth,xmax-xm,xmax,ymin,ymax-ym,zmin,zmax-zm); } } int i=1; //先序遍歷八叉樹 template <class T> void preOrder( OctreeNode<T> * & p) { if(p) { cout<<i<<".當前節點的值為:"<<p->data<<"\n座標為:"; cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; i+=1; cout<<endl; preOrder(p->top_left_front); preOrder(p->top_left_back); preOrder(p->top_right_front); preOrder(p->top_right_back); preOrder(p->bottom_left_front); preOrder(p->bottom_left_back); preOrder(p->bottom_right_front); preOrder(p->bottom_right_back); cout<<endl; } } //求八叉樹的深度 template<class T> int depth(OctreeNode<T> *& p) { if(p == NULL) return -1; int h =depth(p->top_left_front); return h+1; } //計算單位長度,為查詢點做準備 int cal(int num) { int result=1; if(1==num) result=1; else { for(int i=1;i<num;i++) result=2*result; } return result; } //查詢點 int maxdepth=0; int times=0; static double xmin=0,xmax=0,ymin=0,ymax=0,zmin=0,zmax=0; int tmaxdepth=0; double txm=1,tym=1,tzm=1; template<class T> void find(OctreeNode<T> *& p,double x,double y,double z) { double xm=(p->xmax-p->xmin)/2; double ym=(p->ymax-p->ymin)/2; double zm=(p->ymax-p->ymin)/2; times++; if(x>xmax || x<xmin|| y>ymax || y<ymin || z>zmax || z<zmin) { cout<<"該點不在場景中!"<<endl; return; } 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 ) { cout<<endl<<"找到該點!"<<"該點位於"<<endl; cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; cout<<"節點內!"<<endl; cout<<"共經過"<<times<<"次遞迴!"<<endl; } else if(x<(p->xmax-xm) && y<(p->ymax-ym) &&z<(p->zmax-zm)) { cout<<"當前經過節點座標:"<<endl; cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; cout<<endl; find(p->bottom_left_back,x,y,z); } else if(x<(p->xmax-xm) && y<(p->ymax-ym) &&z>(p->zmax-zm)) { cout<<"當前經過節點座標:"<<endl; cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; cout<<endl; find(p->top_left_back,x,y,z); } else if(x>(p->xmax-xm) && y<(p->ymax-ym) &&z<(p->zmax-zm)) { cout<<"當前經過節點座標:"<<endl; cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; cout<<endl; find(p->bottom_right_back,x,y,z); } else if(x>(p->xmax-xm) && y<(p->ymax-ym) &&z>(p->zmax-zm)) { cout<<"當前經過節點座標:"<<endl; cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; cout<<endl; find(p->top_right_back,x,y,z); } else if(x<(p->xmax-xm) && y>(p->ymax-ym) &&z<(p->zmax-zm)) { cout<<"當前經過節點座標:"<<endl; cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; cout<<endl; find(p->bottom_left_front,x,y,z); } else if(x<(p->xmax-xm) && y>(p->ymax-ym) &&z>(p->zmax-zm)) { cout<<"當前經過節點座標:"<<endl; cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; cout<<endl; find(p->top_left_front,x,y,z); } else if(x>(p->xmax-xm) && y>(p->ymax-ym) &&z<(p->zmax-zm)) { cout<<"當前經過節點座標:"<<endl; cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; cout<<endl; find(p->bottom_right_front,x,y,z); } else if(x>(p->xmax-xm) && y>(p->ymax-ym) &&z>(p->zmax-zm)) { cout<<"當前經過節點座標:"<<endl; cout<<"xmin: "<<p->xmin<<" xmax: "<<p->xmax; cout<<"ymin: "<<p->ymin<<" ymax: "<<p->ymax; cout<<"zmin: "<<p->zmin<<" zmax: "<<p->zmax; cout<<endl; find(p->top_right_front,x,y,z); } } //main函式 int main () { OctreeNode<double> *rootNode = NULL; int choiced = 0; while(true) { system("cls"); cout<<"請選擇操作:\n"; cout<<"1.建立八叉樹 2.先序遍歷八叉樹\n"; cout<<"3.檢視樹深度 4.查詢節點 \n"; cout<<"0.退出\n\n"; cin>>choiced; if(choiced == 0) return 0; else if(choiced == 1) { system("cls"); cout<<"請輸入最大遞迴深度:"<<endl; cin>>maxdepth; cout<<"請輸入外包盒座標,順序如下:xmin,xmax,ymin,ymax,zmin,zmax"<<endl; cin>>xmin>>xmax>>ymin>>ymax>>zmin>>zmax; if(maxdepth>=0|| xmax>xmin || ymax>ymin || zmax>zmin || xmin>0 || ymin>0||zmin>0) { tmaxdepth=cal(maxdepth); txm=(xmax-xmin)/tmaxdepth; tym=(ymax-ymin)/tmaxdepth; tzm=(zmax-zmin)/tmaxdepth; createOctree(rootNode,maxdepth,xmin,xmax,ymin,ymax,zmin,zmax); } else { cout<<"輸入錯誤!"; return 0; } } else if(choiced == 2) { system("cls"); cout<<"先序遍歷八叉樹結果:/n"; i=1; preOrder(rootNode); cout<<endl; system("pause"); } else if(choiced == 3) { system("cls"); int dep =depth(rootNode); cout<<"此八叉樹的深度為"<<dep+1<<endl; system("pause"); } else if(choiced == 4) { system("cls"); cout<<"請輸入您希望查詢的點的座標,順序如下:x,y,z\n"; double x,y,z; cin>>x>>y>>z; times=0; cout<<endl<<"開始搜尋該點……"<<endl; find(rootNode,x,y,z); system("pause"); } else { system("cls"); cout<<"\n\n錯誤選擇!\n"; system("pause"); } } }
此上轉載出處:
我將上述程式碼除錯了一下,改動了一些(原樓主是自動給每個節點資料都輸入9,測試起來比較方便,我改成了自己輸入,輸入數較多的時候比較麻煩),除錯過程中出現錯誤(我用的是VC6.0),把標頭檔案#include"stdafx.h"註釋掉之後就可以正常運行了。原樓主有點粗心,把\n打成了/n,我斗膽改了過來~這段程式碼用的遞迴函式比較多。