数据结构介绍

首先介绍BVH树的数据结构:

//BVH(Bounding Volumn Hierachy,Chunk means cube)
struct rcChunkyTriMesh
{inline rcChunkyTriMesh() : nodes(0), nnodes(0), tris(0), ntris(0), maxTrisPerChunk(0) {};inline ~rcChunkyTriMesh() { delete [] nodes; delete [] tris; }rcChunkyTriMeshNode* nodes;//这里用一个数组表示树的节点,nodes代表根节点int nnodes;//节点总数int* tris;//一个数组,记录了BVH树里的三角形索引值(具体的顶点数据仍然存放在load模型时存储的数据中)int ntris;//三角形的总数int maxTrisPerChunk;//一个叶节点,也就是一个Cube里面最多存储的三角形的数量private:// Explicitly disabled copy constructor and copy assignment operator.rcChunkyTriMesh(const rcChunkyTriMesh&);rcChunkyTriMesh& operator=(const rcChunkyTriMesh&);
};

这里的Chunk就是Cube的意思,代表3D空间的一个Cube,不过这里的Cube是长方体,不是正方体

具体节点Node的数据结构如下:

  • bmin和bmax记录了这个节点对应的2D的Cube的大小
  • i,若i>=0,则节点是叶子节点,i的值为这一部分三角形在BVH的tris数组中的位置索引,若 i<0,则节点不是叶子节点,此时i的大小代表包括自身在内的节点数量,比如一个BVH树一共有15个节点,则根节点的i值为-15
  • n,如果是叶节点,则n代表叶节点所含的三角形个数,如果不是叶节点,则n这个值没有意义
struct rcChunkyTriMeshNode
{float bmin[2];//size of the box of the nodefloat bmax[2];int i;//i>0,means leaf node ,i<0 means non-leaf nodeint n;//the number of triangles the node contains
};

创建BVH

在创建BVH树之前,需要拿到场景的三角形数据float *verts和三角面数据int* tris,具体步骤如下:
1. 创建BVH树
创建BVH,同时分配内存,BVH的数据信息为Nodes节点的信息和存储的三角形索引数组信息,代码如下所示:

// 三角形数除以每个chunk的三角形数,得到chunk数
int nchunks = (ntris + trisPerChunk-1) / trisPerChunk;// 由于每个节点存储的三角形数在[trisPerChunk/2, trisPerChunk]间
// trisPerChunk+1个三角形就能占用两个叶子Node和两个父节点的内存
// 所以每个chunk需要分配四个Node节点,保证bvh有足够的内存
cm->nodes = new rcChunkyTriMeshNode[nchunks*4];
if (!cm->nodes)return false;// 为VBH树分配了tris的内存
cm->tris = new int[ntris*3];
if (!cm->tris)return false;cm->ntris = ntris;

2. 获取所有三角形的AABB包围盒
new一个数组,数组大小为场景里三角形的个数,这个数组用来记录所有三角形的2D的AABB,代码如下所示:

// 代表2D的AABB
struct BoundsItem
{float bmin[2];float bmax[2];int i;
};// 为每一个模型里的三角形,创建一个2D的AABB
// Build tree
BoundsItem* items = new BoundsItem[ntris];
if (!items)return false;// 遍历每一个三角形,计算其AABB,并记录三角形在原tris里的索引
for (int i = 0; i < ntris; i++)
{const int* t = &tris[i*3];BoundsItem& it = items[i];it.i = i;// Bounds记录了该Bounds对应的三角形在源数据中的索引// Calc triangle XZ bounds.it.bmin[0] = it.bmax[0] = verts[t[0]*3+0];//x方向it.bmin[1] = it.bmax[1] = verts[t[0]*3+2];//z方向for (int j = 1; j < 3; ++j){const float* v = &verts[t[j]*3];if (v[0] < it.bmin[0]) it.bmin[0] = v[0]; if (v[2] < it.bmin[1]) it.bmin[1] = v[2]; if (v[0] > it.bmax[0]) it.bmax[0] = v[0]; if (v[2] > it.bmax[1]) it.bmax[1] = v[2]; }
}

3. 进行树的划分
这里划分的目标就是三角形,而且这些三角形都在树的tris数组里,具体的划分方法也很简单,当前节点从树的根节点开始:

  • 对于一个三角形集合,如果这个集合的三角形个数小于一个节点能存放的最大三角形个数,则当前节点为叶节点,节点存储的三角形集合在bvh的tris数组的[node.i, node.i + node.n)范围内,同时节点需要记录存储这些三角形的最小AABB。
  • 对于一个三角形集合,如果这个集合的三角形个数大于一个节点能存放的最大三角形个数,则当前节点为非叶节点,需要把这些三角形进行分类,这里根据三角形对应AABB的bmin的某一值进行排序后,再把原来的三角形集合一分为二,再次进行上一步的处理

所以这是一个深度递归的过程,建立BVH树的代码如下:

int curTri = 0;
int curNode = 0;
// 划分树
subdivide(items, ntris, 0, ntris, trisPerChunk, curNode, cm->nodes, nchunks*4, curTri, cm->tris, tris);

subdivide函数如下:

// 这是一个递归函数,用来创建BVH树,输入的items代表三角形对应的bounds数组
static void subdivide(BoundsItem* items, int nitems, int imin, int imax, int trisPerChunk,int& curNode, rcChunkyTriMeshNode* nodes, const int maxNodes,int& curTri, int* outTris, const int* inTris)
{// imin和imax代表在tris里的范围,也就是处理tris[imin]到tris[imax]这一范围内的三角形int inum = imax - imin;int icur = curNode;if (curNode > maxNodes)return;// 获取树里面的curNode索引对应的Node节点rcChunkyTriMeshNode& node = nodes[curNode++];// 如果所给的范围内的三角形数小于一个Cube能存储的最大的三角形数,那就全放到这个节点下if (inum <= trisPerChunk){// 计算这一块区间的AABB对应的总AABB// LeafcalcExtends(items, nitems, imin, imax, node.bmin, node.bmax);// Copy triangles.node.i = curTri;// 记录下这一Cube范围内对应的Node所含的三角形索引在源数据中的位置node.n = inum;// 记录下这一节点内三角形的个数// 存储三角形的索引数据,放到bvh树里for (int i = imin; i < imax; ++i){const int* src = &inTris[items[i].i*3];int* dst = &outTris[curTri*3];curTri++;dst[0] = src[0];dst[1] = src[1];dst[2] = src[2];}}else{// 如果不可以放在同一个cube里,那么需要进行二分// SplitcalcExtends(items, nitems, imin, imax, node.bmin, node.bmax);int    axis = longestAxis(node.bmax[0] - node.bmin[0],node.bmax[1] - node.bmin[1]);// 沿着长边进行排序if (axis == 0){// Sort along x-axis,根据BoundsItem的bmin的x从小到大,把Items进行排序qsort(items+imin, static_cast<size_t>(inum), sizeof(BoundsItem), compareItemX);}else if (axis == 1){// Sort along y-axisqsort(items+imin, static_cast<size_t>(inum), sizeof(BoundsItem), compareItemY);}// 取中点,对两边各自建立新的树int isplit = imin+inum/2;// Leftsubdivide(items, nitems, imin, isplit, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris);// Rightsubdivide(items, nitems, isplit, imax, trisPerChunk, curNode, nodes, maxNodes, curTri, outTris, inTris);// 当走到这里的时候,划分子树,说明此节点不为叶节点,所以其i值是负的int iescape = curNode - icur;// Negative index means escape.实际上就是该节点下的所有子节点数(包括该节点自身)node.i = -iescape;}
}

BVH树的应用

至此,一个BVH树就创建完了,代码也不复杂,下面举一个具体应用的代码,给点一个2D的长方形,找到与其相交的叶子节点(如果再想找与其相交的三角形,再去遍历其内部的三角形就可以了)

int rcGetChunksOverlappingSegment(const rcChunkyTriMesh* cm, float p[2], float q[2], int* ids, const int maxIds)
{// Traverse treeint i = 0;int n = 0;while (i < cm->nnodes){const rcChunkyTriMeshNode* node = &cm->nodes[i];const bool overlap = checkOverlapSegment(p, q, node->bmin, node->bmax);const bool isLeafNode = node->i >= 0;if (isLeafNode && overlap){if (n < maxIds){ids[n] = i;n++;}}if (overlap || isLeafNode)i++;else{// 如果父节点的AABB与Rect不相交,说明所有的子节点也不相交,可以直接跳过子树部分const int escapeIndex = -node->i;i += escapeIndex;}}return n;
}

一点疑问

BVH的子树的AABB是否相交
我认为这里的AABB应该是有可能相交的,因为排序三角形的时候是按照其AABB的bmin的x或z值排序的,三角形如果很大,可能会导致一个非叶节点下的俩叶子节点的AABB相交,如下代码所示:

static int compareItemX(const void* va, const void* vb)
{const BoundsItem* a = (const BoundsItem*)va;const BoundsItem* b = (const BoundsItem*)vb;if (a->bmin[0] < b->bmin[0])//排序只看bmin的x值return -1;if (a->bmin[0] > b->bmin[0])return 1;return 0;
}static int compareItemY(const void* va, const void* vb)
{const BoundsItem* a = (const BoundsItem*)va;const BoundsItem* b = (const BoundsItem*)vb;if (a->bmin[1] < b->bmin[1])return -1;if (a->bmin[1] > b->bmin[1])return 1;return 0;
}// 如果不可以放在同一个cube里,那么需要进行切分
// Split
calcExtends(items, nitems, imin, imax, node.bmin, node.bmax);
int axis = longestAxis(node.bmax[0] - node.bmin[0], node.bmax[1] - node.bmin[1]);      // 沿着长边进行排序
if (axis == 0)
{// Sort along x-axis,根据BoundsItem的bmin的x从小到大,把Items进行排序qsort(items+imin, static_cast<size_t>(inum), sizeof(BoundsItem), compareItemX);
}
else if (axis == 1)
{// Sort along y-axisqsort(items+imin, static_cast<size_t>(inum), sizeof(BoundsItem), compareItemY);
}

可参考的链接

https://www.jianshu.com/p/074a4f7aca59
https://blog.csdn.net/u012138730/article/details/79928505
https://www.cnblogs.com/lookof/p/3546320.html
https://www.cnblogs.com/wickedpriest/p/12269564.html
维基百科Bounding volume hierarchy

Recast Demo中BVH树的构建相关推荐

  1. 【算法】FLANN中kd树构建和查询的简明分析

    flann源码参考:flann: https://github.com/flann-lib/flannsudo apt install libflann-dev 目录 K-最近邻搜索(K-Neares ...

  2. R语言使用party包中的ctree函数构建条件推理决策树的流程和步骤、条件推理决策树是传统决策树的一个重要变体、条件推理树的分裂是基于显著性测试而不是熵/纯度/同质性度量来选择分裂

    R语言使用party包中的ctree函数构建条件推理决策树的流程和步骤(Conditional inference trees).条件推理决策树是传统决策树的一个重要变体.条件推理树的分裂是基于显著性 ...

  3. 空间数据结构(四叉树、八叉树、BVH树、BSP树、k-d树)

    转载地址:https://www.cnblogs.com/KillerAery/p/10878367.html 1. 前言: 在游戏程序中,利用空间数据结构加速计算往往是非常重要的优化思想,空间数据结 ...

  4. 空间数据结构(四叉树/八叉树/BVH树/BSP树/k-d树)

    转载说明: 原作者:KillerAery 出处:https://www.cnblogs.com/KillerAery/p/10878367.html 1 四叉树/八叉树 (Quadtree/Octre ...

  5. DOM树和CSSOM树的构建和渲染

    页面的渲染过程 当我们在浏览器里输入一个 URL 后,最终会呈现一个完整的网页.会经历以下几个步骤: 1.HTML 的加载 页面上输入 URL 后,会先拿到 HTML 文件.HTML是一个页面的基础, ...

  6. Demo:基于 Flink SQL 构建流式应用

    摘要:上周四在 Flink 中文社区钉钉群中直播分享了<Demo:基于 Flink SQL 构建流式应用>,直播内容偏向实战演示.这篇文章是对直播内容的一个总结,并且改善了部分内容,比如除 ...

  7. cJSON使用教程(树外构建 out of tree build 概念)(组包概念)

    JSON基础:包括组包的概念等 Github:DaveGamble/cJSON https://github.com/DaveGamble/cJSON 文章目录 Github:DaveGamble/c ...

  8. 【算法学习笔记】哈夫曼树的构建和哈夫曼编码的实现代码

    介绍 哈夫曼(Haffman)这种方法的基本思想如下: ①由给定的n个权值{W1,W2,-,Wn}构造n棵只有一个叶子结点的二叉树,从而得到一个二叉树的集合F={T1,T2,-,Tn}. ②在F中选取 ...

  9. maven 构建依赖树_Maven构建依赖项

    maven 构建依赖树 熟悉发行版和快照依赖关系的Maven和Gradle用户可能不了解TeamCity快照依赖关系,或者假定他们与Maven相关(这是不正确的). 熟悉工件和快照相关性的TeamCi ...

最新文章

  1. 69张图看懂Elon Musk的脑机接口芯片项目
  2. JS获取上传文件的大小
  3. 云炬Android开发笔记 5-5,6Loading框架集成与完善AVLoadingIndicatorView
  4. InfoQ专访:人工智能时代,什么才是你的最大竞争力?
  5. java 防止表单重复提交
  6. java 注解加载配置文件_Spring的Java配置方式和读取properties配置文件
  7. HUT-XXXX Strange display 容斥定理,线性规划
  8. python语言用什么编译器_如何修改python语言pycharm工具的默认编译器
  9. r语言初学者指南_由R入统:R语言统计学类书籍推荐
  10. android平板开机动画,实战——Android5.0开机动画定制
  11. editview软键盘弹出和隐藏
  12. 如何处理大数据量的查询
  13. 利用 EFS 快速搭建 NFS 文件系统
  14. 1.《阿西莫夫:机器人短篇全集》
  15. C. Anton and Fairy Tale
  16. 怎么查看笔记本内存条型号_新买的笔记本如何查看笔记本内存条型号有哪些方法...
  17. 字幕文件srt处理之pysrt
  18. python3编写人工智能_人工智能学习第三章 编写第一个Python程序 及概念
  19. uni-app做app自定义弹窗实现
  20. python自动化生成请假条

热门文章

  1. idea 跑springboot项目不报log日志
  2. maven mybatis generator自动生成代码
  3. 【安全服务】windows/Linux安全基线检查|等保3.0安全技术测评指导
  4. AI快车道深圳专场,揭秘CV目标检测核心技术
  5. java泛型属性_java泛型介绍
  6. Spark一路火花带闪电——Spark常用算子(参数及其返回值)探究
  7. (最详细)小米MIX的Usb调试模式在哪里打开的教程
  8. php excel获取合并单元格的内容,并自动向上获取第一个有值的单元格数据
  9. 基于15单片机模拟自动售水机——蓝桥杯
  10. linux项目实施总结,[转载]SAP项目实施总结