目录
在描述三维场景的过程中常常用到一种名为八叉树的数据结构。描述三维空间的八叉树和描述二维空间的四叉树有相似之处,二维空间中正方形可以被分为四个相同形状的正方形,而三维空间中正方体可以被分为八个形状相同的正方体。
八叉树的每个结点表示一个正方体的体积元素,每一个结点有八个子节点,这种用于描述三维空间的树装结构叫做八叉树。
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");
- }
- }
- }
main函数
- // osgPro227.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
-
- #include <osg/Group>
- #include <osgDB/ReadFile>
- #include <osgUtil/PrintVisitor>
- #include <osgViewer/ViewerEventHandlers>
- #include <osgViewer/Viewer>
- #include <osgViewer/ViewerEventHandlers> //事件监听
- #include <osgGA/StateSetManipulator> //事件响应类,对渲染状态进行控制
-
- #include "OctreeBuilder.h"
-
- #pragma comment(lib, "OpenThreadsd.lib")
- #pragma comment(lib, "osgd.lib")
- #pragma comment(lib, "osgDBd.lib")
- #pragma comment(lib, "osgUtild.lib")
- #pragma comment(lib, "osgGAd.lib")
- #pragma comment(lib, "osgViewerd.lib")
- #pragma comment(lib, "osgTextd.lib")
-
- float randomValue(float min, float max)
- {
- return (min + (float)rand() / (RAND_MAX + 1.0f) * (max - min));
- }
-
- osg::Vec3 randomVector(float min, float max)
- {
- return osg::Vec3(randomValue(min, max),randomValue(min, max),randomValue(min, max));
- }
- class PrintNameVisitor : public osgUtil::PrintVisitor
- {
- public:
- PrintNameVisitor(std::ostream& out) : osgUtil::PrintVisitor(out) {}
-
- void apply(osg::Node& node)
- {
- if (!node.getName().empty())
- {
- output() << node.getName() << std::endl;
- enter();
- traverse(node);
- leave();
- }
- else osgUtil::PrintVisitor::apply(node);
- }
- };
-
- int main(int argc, char** argv)
- {
- osg::BoundingBox globalBound;
- std::vector<OctreeBuilder::ElementInfo> globalElements;
- for (unsigned int i = 0; i < 5000; ++i)
- {
- osg::Vec3 pos = randomVector(-500.0f, 500.0f);
- float radius = randomValue(0.5f, 20.0f);
- std::stringstream ss; ss << "Ball-" << i + 1;
-
- osg::Vec3 min = pos - osg::Vec3(radius, radius, radius);
- osg::Vec3 max = pos + osg::Vec3(radius, radius, radius);
- osg::BoundingBox region(min, max);
- globalBound.expandBy(region);
- globalElements.push_back(OctreeBuilder::ElementInfo(ss.str(), region));
- }
-
- OctreeBuilder octree;
- osg::ref_ptr<osg::Group> root = octree.build(0, globalBound, globalElements);
-
- std::ofstream out("octree_output.txt");
- PrintNameVisitor printer(out);
- root->accept(printer);
-
- osg::ref_ptr <osgViewer::Viewer> viewer = new osgViewer::Viewer;
- viewer->setSceneData(root.get());
- viewer->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));
- viewer->addEventHandler(new osgViewer::StatsHandler());//实现状态信息统计
- viewer->addEventHandler(new osgViewer::WindowSizeHandler());
-
- return viewer->run();
- }
OctreeBuilder.h 头文件
- #ifndef H_COOKBOOK_CH8_OCTREEBUILDER
- #define H_COOKBOOK_CH8_OCTREEBUILDER
-
- #include <osg/Geode>
- #include <osg/LOD>
-
- class OctreeBuilder
- {
- public:
- OctreeBuilder() : _maxChildNumber(16), _maxTreeDepth(8), _maxLevel(0) {}
- int getMaxLevel() const { return _maxLevel; }
-
- void setMaxChildNumber( int max ) { _maxChildNumber = max; }
- int getMaxChildNumber() const { return _maxChildNumber; }
-
- void setMaxTreeDepth( int max ) { _maxTreeDepth = max; }
- int getMaxTreeDepth() const { return _maxTreeDepth; }
-
- typedef std::pair<std::string, osg::BoundingBox> ElementInfo;
- osg::Group* build( int depth, const osg::BoundingBox& total,
- std::vector<ElementInfo>& elements );
-
- protected:
- osg::LOD* createNewLevel( int level, const osg::Vec3& center, float radius );
- osg::Node* createElement( const std::string& id, const osg::Vec3& center, float radius );
- osg::Geode* createBoxForDebug( const osg::Vec3& max, const osg::Vec3& min );
-
- int _maxChildNumber;
- int _maxTreeDepth;
- int _maxLevel;
- };
-
- #endif
OctreeBuilder.cpp
- #include <windows.h>
- #include <iostream>
- #include <fstream>
- #include <sstream>
- using namespace std;
-
- #include <osg/ShapeDrawable>
- #include <osg/Geometry>
- #include <osg/PolygonMode>
- #include "OctreeBuilder.h"
-
- osg::Group* OctreeBuilder::build( int depth, const osg::BoundingBox& total,std::vector<ElementInfo>& elements )
- {
- int s[3]; // axis sides (0 or 1)
-
- //存放当前包围盒的最大、中间、最小点,以为划分八叉树做准备
- osg::Vec3 extentSet[3] = {
- total._min,
- (total._max + total._min) * 0.5f,
- total._max
- };
-
- std::vector<ElementInfo> childData;
- //遍历父结点的所有孩子,让包含在当前盒子里的,不完全包含但是中点在盒子里的,都压入到当前盒子的子结点
- for ( unsigned int i=0; i<elements.size(); ++i )
- {
- const ElementInfo& obj = elements[i];
- if ( total.contains(obj.second._min) && total.contains(obj.second._max) )
- childData.push_back( obj );
- else if ( total.intersects(obj.second) )
- {
- osg::Vec3 center = (obj.second._max + obj.second._min) * 0.5f;
- if (total.contains(center))
- {
- childData.push_back(obj);
- }
- }
- }
-
- //如果当前结点的孩子数量已经达标,或者层数已经达标,则认为是叶结点
- bool isLeafNode = false;
- if ((int)childData.size() <= _maxChildNumber || depth > _maxTreeDepth)
- {
- isLeafNode = true;
- }
-
- //当前八叉树根
- osg::ref_ptr<osg::Group> group = new osg::Group;
-
- //如果不是叶结点,继续分,把空间一分为八
- if ( !isLeafNode )
- {
- osg::ref_ptr<osg::Group> childNodes[8];
-
- //空间一分为八2*2*2
- for ( s[0]=0; s[0]<2; ++s[0] ) //x
- {
- for ( s[1]=0; s[1]<2; ++s[1] ) //y
- {
- for ( s[2]=0; s[2]<2; ++s[2] ) //z
- {
- // Calculate the child extent
- //extentSet 0是最小,1是中间,2是最大
- //下面这个小算法有点磨人,分别求出min和max的x, y, z自己好好推几个试试
- osg::Vec3 min, max;
- for ( int a=0; a<3; ++a )
- {
- min[a] = (extentSet[s[a] + 0])[a];
- max[a] = (extentSet[s[a] + 1])[a];
- }
-
- //这么求id是为了确保唯一性
- int id = s[0] + (2 * s[1]) + (4 * s[2]);
- childNodes[id] = build( depth+1, osg::BoundingBox(min, max), childData );
- }
- }
- }
-
- //八个子结点构建完毕后,加入到根结点当中
- for ( unsigned int i=0; i<8; ++i )
- {
- if ( childNodes[i] && childNodes[i]->getNumChildren() )
- group->addChild( childNodes[i] );
- }
- }
- else //找到叶结点,递归就结束了
- {
- for ( unsigned int i=0; i<childData.size(); ++i )
- {
- const ElementInfo& obj = childData[i];
- osg::Vec3 center = (obj.second._max + obj.second._min) * 0.5;
- float radius = (obj.second._max - obj.second._min).length() * 0.5f;
- //创建一个球
- group->addChild( createElement(obj.first, center, radius) );
- }
- }
-
- osg::Vec3 center = (total._max + total._min) * 0.5;
- float radius = (total._max - total._min).length() * 0.5f;
-
- //最后创建一个LOD,离的远了显示调试盒子,离的近了显示分的组
- osg::LOD* level = createNewLevel( depth, center, radius );
- level->insertChild( 0, createBoxForDebug(total._max, total._min) ); // For debug use
- level->insertChild( 1, group.get() );
- return level;
- }
-
- osg::LOD* OctreeBuilder::createNewLevel( int level, const osg::Vec3& center, float radius )
- {
- osg::ref_ptr<osg::LOD> lod = new osg::LOD;
- lod->setCenterMode( osg::LOD::USER_DEFINED_CENTER );
- lod->setCenter( center );
- lod->setRadius( radius );
- lod->setRange( 0, radius * 5.0f, FLT_MAX );
- lod->setRange( 1, 0.0f, radius * 5.0f );
-
- if ( _maxLevel<level ) _maxLevel = level;
- return lod.release();
- }
-
- osg::Node* OctreeBuilder::createElement( const std::string& id, const osg::Vec3& center, float radius )
- {
- osg::ref_ptr<osg::Geode> geode = new osg::Geode;
- geode->addDrawable( new osg::ShapeDrawable(new osg::Sphere(center, radius)) );
- geode->setName( id );
- return geode.release();
- }
-
- osg::Geode* OctreeBuilder::createBoxForDebug( const osg::Vec3& max, const osg::Vec3& min )
- {
- osg::Vec3 dir = max - min;
- osg::ref_ptr<osg::Vec3Array> va = new osg::Vec3Array(10);
- (*va)[0] = min + osg::Vec3(0.0f, 0.0f, 0.0f);
- (*va)[1] = min + osg::Vec3(0.0f, 0.0f, dir[2]);
- (*va)[2] = min + osg::Vec3(dir[0], 0.0f, 0.0f);
- (*va)[3] = min + osg::Vec3(dir[0], 0.0f, dir[2]);
- (*va)[4] = min + osg::Vec3(dir[0], dir[1], 0.0f);
- (*va)[5] = min + osg::Vec3(dir[0], dir[1], dir[2]);
- (*va)[6] = min + osg::Vec3(0.0f, dir[1], 0.0f);
- (*va)[7] = min + osg::Vec3(0.0f, dir[1], dir[2]);
- (*va)[8] = min + osg::Vec3(0.0f, 0.0f, 0.0f);
- (*va)[9] = min + osg::Vec3(0.0f, 0.0f, dir[2]);
-
- osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
- geom->setVertexArray( va.get() );
- geom->addPrimitiveSet( new osg::DrawArrays(GL_QUAD_STRIP, 0, 10) );
-
- osg::ref_ptr<osg::Geode> geode = new osg::Geode;
- geode->addDrawable( geom.get() );
- geode->getOrCreateStateSet()->setAttribute(
- new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE) );
- geode->getOrCreateStateSet()->setMode( GL_LIGHTING, osg::StateAttribute::OFF );
- return geode.release();
- }
