八叉树
维基释义:八叉树(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 }