八叉树场景管理

article/2025/10/28 23:01:38

目录

  • 什么是八叉树(八叉树的数据结构)
  • 八叉树的图例
  • 八叉树的实现算法
  • 八叉树的场景管理器代码实现
  • 八叉树的应用场景

1.什么是八叉树——八叉树的数据结构

  八叉树是一个树形结构,它的特点就是每个节点正好拥有八个子节点。它的这个结构特点正好能把空间立方体平均分成对称八份。利用这个特性,八叉树经常用在3D图形学中或者3D游戏中的碰撞检测,场景管理等。

Each node in an octree subdivides the space it represents into eight octants. In a point region (PR) octree, the node stores an explicit three-dimensional point, which is the “center” of the subdivision for that node; the point defines one of the corners for each of the eight children. In a matrix based (MX) octree, the subdivision point is implicitly the center of the space the node represents. The root node of a PR octree can represent infinite space; the root node of an MX octree must represent a finite bounded space so that the implicit centers are well-defined. Note that Octrees are not the same as k-d trees: k-d trees split along a dimension and octrees split around a point. Also k-d trees are always binary, which is not the case for octrees. By using a depth-first search the nodes are to be traversed and only required surfaces are to be viewed.


2.八叉树的图例

image

  • 图片来自wikipedia

3.八叉树的实现算法

  1. 设定最大递归深度
  2. 找出场景的最大尺寸,并以此尺寸建立第一个立方体
  3. 依序将单位元元素丢入能被包含且没有子节点的立方体
  4. 若没有达到最大递归深度,就进行细分八等份,再将该立方体所装的单位元元素全部分担给八个子立方体
  5. 若发现子立方体所分配到的单位元元素数量不为零且跟父立方体是一样的,则该子立方体停止细分,因为跟据空间分割理论,细分的空间所得到的分配必定较少,若是一样数目,则再怎么切数目还是一样,会造成无穷切割的情形。
  6. 重复3,直到达到最大递归深度。
  • 选自百度百科

我们逐条分析下:

  1. 设定最大递归深度即设置树的最大深度。
    1层深度只有一个根节点,2层深度1个根节点和8个子节点,3层深度则对2层8个节点再生成8^8个节点……可见节点的个数随着层数的增加呈(8)指数增长,那么计算量也会指数增加。
  2. 找出场景的最大尺寸,并以此尺寸简历第一个立方体。
    这个比较好理解,即创建最大尺寸的立方体,这个立方体必须包含场景中所有的物体,接下来再对这个立方体进行八叉树细分。
  3. 第三和第四条的意思就是从根节点开始,对场景物体进行分类,根据位置和大小丢进八叉树的叶子节点中。第五条是一个优化方案,即如果子节点和父节点被分到的物体一样多,那么就相当于这个节点所有物体的位置大小都差不多,没有必要再细分下去(这里我有疑问,我的观点是没有到叶子节点都不能认为"再怎么切数目还是一样")。
  4. 递归。

4.八叉树的场景管理器代码实现

对于八叉树的代码实现,github上找到一个非常简单的实现

我把主要实现代码贴在这里:

#ifndef Octree_H
#define Octree_H#include <cstddef>
#include <vector>
#include "OctreePoint.h"namespace brandonpelfrey {/**!**/class Octree {// Physical position/size. This implicitly defines the bounding // box of this nodeVec3 origin;         //! The physical center of this nodeVec3 halfDimension;  //! Half the width/height/depth of this node// The tree has up to eight children and can additionally store// a point, though in many applications only, the leaves will store data.Octree *children[8]; //! Pointers to child octantsOctreePoint *data;   //! Data point to be stored at a node/*Children follow a predictable pattern to make accesses simple.Here, - means less than 'origin' in that dimension, + means greater than.child:	0 1 2 3 4 5 6 7x:      - - - - + + + +y:      - - + + - - + +z:      - + - + - + - +*/public:Octree(const Vec3& origin, const Vec3& halfDimension) : origin(origin), halfDimension(halfDimension), data(NULL) {// Initially, there are no childrenfor(int i=0; i<8; ++i) children[i] = NULL;}Octree(const Octree& copy): origin(copy.origin), halfDimension(copy.halfDimension), data(copy.data) {}~Octree() {// Recursively destroy octantsfor(int i=0; i<8; ++i) delete children[i];}// Determine which octant of the tree would contain 'point'int getOctantContainingPoint(const Vec3& point) const {int oct = 0;if(point.x >= origin.x) oct |= 4;if(point.y >= origin.y) oct |= 2;if(point.z >= origin.z) oct |= 1;return oct;}bool isLeafNode() const {// This is correct, but overkill. See below./*for(int i=0; i<8; ++i)if(children[i] != NULL) return false;return true;*/// We are a leaf iff we have no children. Since we either have none, or // all eight, it is sufficient to just check the first.return children[0] == NULL;}void insert(OctreePoint* point) {// If this node doesn't have a data point yet assigned // and it is a leaf, then we're done!if(isLeafNode()) {if(data==NULL) {data = point;return;} else {// We're at a leaf, but there's already something here// We will split this node so that it has 8 child octants// and then insert the old data that was here, along with // this new data point// Save this data point that was here for a later re-insertOctreePoint *oldPoint = data;data = NULL;// Split the current node and create new empty trees for each// child octant.for(int i=0; i<8; ++i) {// Compute new bounding box for this childVec3 newOrigin = origin;newOrigin.x += halfDimension.x * (i&4 ? .5f : -.5f);newOrigin.y += halfDimension.y * (i&2 ? .5f : -.5f);newOrigin.z += halfDimension.z * (i&1 ? .5f : -.5f);children[i] = new Octree(newOrigin, halfDimension*.5f);}// Re-insert the old point, and insert this new point// (We wouldn't need to insert from the root, because we already// know it's guaranteed to be in this section of the tree)children[getOctantContainingPoint(oldPoint->getPosition())]->insert(oldPoint);children[getOctantContainingPoint(point->getPosition())]->insert(point);}} else {// We are at an interior node. Insert recursively into the // appropriate child octantint octant = getOctantContainingPoint(point->getPosition());children[octant]->insert(point);}}// This is a really simple routine for querying the tree for points// within a bounding box defined by min/max points (bmin, bmax)// All results are pushed into 'results'void getPointsInsideBox(const Vec3& bmin, const Vec3& bmax, std::vector<OctreePoint*>& results) {// If we're at a leaf node, just see if the current data point is inside// the query bounding boxif(isLeafNode()) {if(data!=NULL) {const Vec3& p = data->getPosition();if(p.x>bmax.x || p.y>bmax.y || p.z>bmax.z) return;if(p.x<bmin.x || p.y<bmin.y || p.z<bmin.z) return;results.push_back(data);}} else {// We're at an interior node of the tree. We will check to see if// the query bounding box lies outside the octants of this node.for(int i=0; i<8; ++i) {// Compute the min/max corners of this child octantVec3 cmax = children[i]->origin + children[i]->halfDimension;Vec3 cmin = children[i]->origin - children[i]->halfDimension;// If the query rectangle is outside the child's bounding box, // then continueif(cmax.x<bmin.x || cmax.y<bmin.y || cmax.z<bmin.z) continue;if(cmin.x>bmax.x || cmin.y>bmax.y || cmin.z>bmax.z) continue;// At this point, we've determined that this child is intersecting // the query bounding boxchildren[i]->getPointsInsideBox(bmin,bmax,results);} }}};
}#ifndef OctreePoint_H
#define OctreePoint_H#include "Vec3.h"// Simple point data type to insert into the tree.
// Have something with more interesting behavior inherit
// from this in order to store other attributes in the tree.
class OctreePoint {Vec3 position; 
public:OctreePoint() { }OctreePoint(const Vec3& position) : position(position) { }inline const Vec3& getPosition() const { return position; }inline void setPosition(const Vec3& p) { position = p; }
};#endif#endif

代码很简单,注释也很详细了。在实际的运用过程中还需要优化一下。如果用在场景剔除,那么每个节点具备的属性有AABBox,深度,可见性,孩子节点。如果仅仅是判断场景物体的可见性,那么就不需要先构建八叉树然后再与摄像机进行碰撞判断了,可以在构建的过程中就进行可见性判断。

class HrOctNode
{
public:HrOctNode(const AABBox& aabb, int nDepth, bool bLeafNode = false);~HrOctNode();......void WalkTree(const HrCameraPtr& pCamera, const HrSceneNodePtr& pSceneNode, float fThreshold, int nMaxDepth);......
protected:AABBox m_aabb;//当前深度int m_nDepth;//是否为叶子节点bool m_bLeafNode;//是否重新初始化了AABBbool m_bInitAABB;HrMath::EnumVisibility m_selfNV;std::array<HrOctNode*, 8> m_arrChildren;
};

我们把构造函数改为遍历构造,在构造的过程中就进行可见性判断。具体步骤:

  1. 如果当前节点本来就不可见,那么就不用判断了,不管是否为叶子节点,落在这个节点的所有物体都不可见。
  2. 如果当前是叶子节点,那么判断摄像机和落在这个节点的物体的碰撞(可见性判断),这里优化空间就是如果当前节点完全可见,那么落在这个几点的物体也全部可见。
  3. 如果当前节点不是叶子节点并且可见(全可见或者部分可见),那么继续细分
void HrOctNode::WalkTree(const HrCameraPtr& pCamera, const HrSceneNodePtr& pSceneNode, float fThreshold, int nMaxDepth)
{//先判断是否可见 //如果这个节点本身就不可见 //那么就不用初始化了 落在这个区域的物体也不可见if (!DetectNodeVisible(pCamera, fThreshold)){return;}// If this node doesn't have a data point yet assigned // and it is a leaf, then we're done!if (m_nDepth == nMaxDepth){if (DetectDataVisible(pCamera, fThreshold, pSceneNode->GetTransform()->GetWorldAABBox())){if (pSceneNode->GetFrustumVisible() != HrMath::NV_FULL)pSceneNode->SetFrustumVisible(HrMath::NV_FULL);}}else{if (!m_bInitAABB){InitChildrenNode(nMaxDepth);}const float3& parentCenter = m_aabb.Center();const AABBox& aabb = pSceneNode->GetTransform()->GetWorldAABBox();int mark[6];mark[0] = aabb.Min().x() >= parentCenter.x() ? 1 : 0;mark[1] = aabb.Min().y() >= parentCenter.y() ? 2 : 0;mark[2] = aabb.Min().z() >= parentCenter.z() ? 4 : 0;mark[3] = aabb.Max().x() >= parentCenter.x() ? 1 : 0;mark[4] = aabb.Max().y() >= parentCenter.y() ? 2 : 0;mark[5] = aabb.Max().z() >= parentCenter.z() ? 4 : 0;for (int j = 0; j < 8; ++j){if (j == ((j & 1) ? mark[3] : mark[0])+ ((j & 2) ? mark[4] : mark[1])+ ((j & 4) ? mark[5] : mark[2])){m_arrChildren[j]->WalkTree(pCamera, pSceneNode, fThreshold, nMaxDepth);}}}
}

5.八叉树的应用场景

  八叉树可以用在一些在"场景"中选取部分物体的场合,例如捕鱼中的子弹碰撞判断,也可以用八叉树来实现,只不过要考虑计算量的问题(每物理帧构建八叉树)。八叉树相对来说构建起来非常快,当然是相对的,如果场景很简单,物体也很少,也没有必要过度设计。八叉树的缺点就是占用空间。

  这里有一个关于四叉树八叉树BSP树区别的问题讨论


http://chatgpt.dhexx.cn/article/iZApBjwE.shtml

相关文章

基于八叉树的空间划分及搜索操作

基于八叉树的空间划分及搜索操作 原理CodeCmakeList.txtCPP体素 近邻 搜索K 近邻 搜索半径内 近邻 搜索 Result 原理 建立空间索引在点云数据处理中有着广泛的应用&#xff0c;常见的空间索引一般 是自顶而下逐级划分空间的各种空间索引结构。 比较有代表性的包括 BSP树KD树K…

基于C++的八叉树颜色删减实验

基于八叉树颜色删减实验 一、实验目的及要求 实现真彩色到256色的颜色转换算法 提供的代码&#xff1a; main.cpp &#xff1a;提供了主控函数main &#xff0c;八叉树类octTree 和八叉树节点结构octNode 。 代码的编译&#xff1a; 由于需要使用bmp的信息头和文件头结构…

八叉树 java_图像八叉树量化讲解 Java版本

这篇文章主要讲解八叉树算法的原理&#xff0c;并用java进行实现 1.算法原理 八叉树最早是在1988年&#xff0c;由 M. Gervautz 和 W. Purgathofer 提出&#xff0c;该算法的最大优点是效率高&#xff0c;占用内存少。在图像量化中的思路是&#xff0c;图像rgb通道的值均为8比特…

详解八叉树地图

个人博客&#xff1a;http://www.chenjianqu.com/ 原文链接&#xff1a;http://www.chenjianqu.com/show-102.html 八叉树地图 八叉树地图(OctoMap)就是一种灵活的、压缩的、又能随时更新的地图。八叉树示意图如下&#xff1a; 一个大立方体不断地均匀分成八块&#xff0c;直…

PCL可视化八叉树格网

1 原理 八叉树其实是一种特殊的由上至下的体素&#xff0c;其构造原理在这里不再进行赘述&#xff0c;详细的构造方式可参考博客&#xff1a;https://blog.csdn.net/qq_32867925/article/details/109094719 有时候为了将点云、构造的层次八叉树需要进行可视化&#xff0c;便于…

松散八叉树

1八叉树简述 1.1定义1.2数据1.3树的建立 1.3.1计算包围体的大小与中心点1.3.2判断物体所属的包围盒2松散八叉树 2.1松散八叉树的建立 八叉树简述 定义 八叉树是一种对三维世界进行场景管理的理想的空间数据结构。八叉树中根节点包含一个立方体包围盒。每个非叶子节点都拥有八…

八叉树学习

八叉树学习 八叉树结构八叉树的存储结构1. 规则八叉树&#xff1a;2.线性八叉树&#xff1a;3.一对八式八叉树 参考网站 八叉树结构 八叉树结构是由 Hunter 博士于1978年首次提出的一种数据模型。八叉树结构通过对三维空间的几何实体进行体元剖分&#xff0c;每个体元具有相同…

PCL中的八叉树

目录 &#xff08;1&#xff09;什么是八叉树 &#xff08;2&#xff09;PCL中八叉树的体素形成和PCL中基于八叉树的点云压缩 &#xff08;3&#xff09;基于八叉树的k邻域搜索、半径搜索和体素近邻搜索 &#xff08;4&#xff09;基于八叉树和基于 kd-tree 的k邻域搜索、半…

八叉树 java_java简单实现八叉树图像处理代码示例

一晃工作有段时间了&#xff0c;第一次写博客&#xff0c;有点不知道怎么写&#xff0c;大家将就着看吧&#xff0c;说的有什么不正确的也请大家指正。 最近工作中用到了一个图像压缩的功能。找了一些工具&#xff0c;没有太好的选择。最后选了一个叫jdeli的&#xff0c;奈何效…

八叉树

http://hi.baidu.com/onlywater/blog/item/905c5e162ed18f4021a4e9c1.html 一、八叉树基本原理&#xff1a; 用八叉树来表示三维形体&#xff0c;并研究这种表示下的各种操作以及应用&#xff0c;是进入80年代后开展起来的。这种方法&#xff0c;既可以看成是四叉树方法在三维…

Octree(八叉树)

1. 算法原理 八叉树&#xff08;Octree&#xff09;是一种用于描述三维空间的树状数据结构。八叉树的每个节点表示一个正方体的体积元素&#xff0c;每个节点有八个子节点&#xff0c;将八个子节点所表示的体积元素加在一起就等于父节点的体积。八叉树是四叉树在三维空间上的扩…

【PCL自学:ocTree】八叉树(octree)的原理及应用案例(点云压缩,搜索,空间变化)

PCL中八叉树&#xff08;octree&#xff09;的原理及应用案例 一、什么是八叉树ocTree&#xff1f;1.八叉树原理 二、八叉树应用案例1.点云压缩2.用八叉树进行空间划分和搜索操作3.无序点云数据的空间变化检测 一、什么是八叉树ocTree&#xff1f; 1.八叉树原理 上世纪80年代&…

十进制小数化为二进制小数的方法是什么_十进制转成二进制的两种方式

第一种&#xff1a;用2整除的方式。 用2整除十进制整数&#xff0c;得到一个商和余数&#xff1b;再用2去除商&#xff0c;又会得到一个商和余数&#xff0c;如此重复&#xff0c;直到商为小于1时为止&#xff0c;然后把先得到余数作为二进制数的低位有效位&#xff0c;后得到的…

Python 利用内置函数把二进制小数转换为十进制

如果需要把一个二进制整数转换为十进制整数&#xff0c;只需要简单的一行&#xff1a; int(1101,2) 但如果有一个二进制小数的话&#xff0c;就需要自己实现一个函数了。 不过&#xff0c;许多人是这样写的&#xff1a;(图片取自这里) 可是&#xff0c;由于python本身并不适…

[学习笔记] 二进制小数表示方法

文章目录 科学计数法二进制推广计算机中的小数EXCESS表示系统特殊情况举例&#xff08;float&#xff09;普通情况最大正实数普通情况最小负实数普通情况最小正实数特殊情况最大正实数 科学计数法 科学计数法想必大家都很熟悉了&#xff0c;往往通过如下形式表示一个实数&…

十进制小数化为二进制小数的方法是什么_二进制的转换

二进制是在计算机中常用的一种进制数&#xff0c;其数据用0和1两个数码来表示数据。我们人类常用的是十进制&#xff0c;那么二进制和十进制之间是有一个转换方法的。 二进制转换十进制 一个二进制数转换为十进制数&#xff0c;是比较简单的&#xff0c;其方法就是用每一个位置…

二进制小数的意义

回忆小学学的十进制小数的意义&#xff1a; 15.23这个小数&#xff0c;1是十位&#xff0c;5是个位&#xff0c;2是十分位&#xff0c;3是百分位。这个小数的意义为&#xff1a;&#xff0c;因为最低位为百分位&#xff0c;所以分母是100。 小数末尾加上0或去掉0&#xff0c;小…

浮点数(小数)在计算机中如何用二进制存储?

浮点数在计算机中如何用二进制存储&#xff1f; 前言 前面我有篇博文详解了二进制数&#xff0c;以及如何同二进制数表示整数。但是&#xff0c;计算机处理的不仅仅是整数&#xff0c;还有小数&#xff0c;同样小数在计算机也是用二进制进行存储的&#xff0c;但是&#xff0…

二进制小数的表示

二级制小数分为两大类&#xff1a;1、定点数&#xff1b;2、浮点数。 定点数 定点数&#xff1a; &#xff08;1&#xff09;小数点位置固定不变的数。 &#xff08;2&#xff09;定点数有定点整数和定点小数。 &#xff08;定点整数&#xff1a;小数部分为0&#xff1b;定点…

腾讯TDSQL全时态数据库系统论文入选VLDB

当地时间2019年8月26至30日&#xff0c;VLDB 2019会议在美国加利福尼亚召开&#xff0c;腾讯分布式数据库TDSQL与中国人民大学最新联合研究成果被VLDB 2019接收并将通过长文形式发表。VLDB是国际数据管理与数据库领域顶尖的学术会议之一&#xff0c;这是继去年腾讯TDSQL相似度计…