从零开始学习导航网格#5 rcChunkyTriMesh与BVH

本篇单独来讲recast项目中的一个重要数据结构:BVH
在计算几何中有一个基础的问题:判断一个点是否在场景中的某个几何体内部
如果我们通过枚举场景内的所有几何体,然后依次判断,这显然太暴力了
一种优化思路是将空间划分成若干区域,首先找到目标点所在的区域,这样就可以剔除掉不在这个区域的几何体,从而减少判断次数
BVH(Bounding volume hierarchy)即包围体层次结构,就是一种用树形结构组织管理空间内物体(几何体)的方法
wiki上的例子:

从零开始学习导航网格#5 rcChunkyTriMesh与BVH_第1张图片
BVH

大致可以看出树和原场景包围盒的对应关系:
1.根节点是一个大的包围盒,包含所有的几何体
2.每个叶子节点对应一个独立的几何体
3.每个非叶子节点有若干个子节点,表示这个大的包围盒内部包含了若干个小的包围盒

如何建树:

建树的过程也就是划分空间的过程
首先根节点就是包含所有几何体的最大包围盒,叶子节点是只包含单个几何体的最小包围盒,这是可以预先确定的,也是建树的起点和递归边界
那么我们就需要制定一种策略来进行划分,主要有三种策略:
1.取某一条轴的中点划分
2.按左右子树几何体数量相等的规则划分
3.按表面积启发式算法划分

Recast项目中是采用了第二种策略

下面来看一下Recast是怎么实现BVH的
先说明一下几个重要的参数和数据结构类型

const float* verts;///输入的顶点集合
const int* tris;///输入的三角形集合
int ntris;///输入的三角形个数

///三角形在xz平面投影的包围盒
struct BoundsItem
{
    float bmin[2];
    float bmax[2];
    int i;///由于会对包围盒按坐标排序,id记录了这个包围盒对应的输入三角形的id
};

///BVH的节点
struct rcChunkyTriMeshNode
{
    float bmin[2];///包含区域内所有三角形的包围盒
    float bmax[2];
    int i;///i为正表示是叶子节点;i为负表示是非叶子节点,大小等于在数组内的覆盖范围
    int n;///节点内包含的三角形个数
};

///一棵BVH树
struct rcChunkyTriMesh
{
    rcChunkyTriMeshNode* nodes;///根节点
    int nnodes;///节点个数
    int* tris;///按区域划分排序后的三角形集合
    int ntris;///三角形个数
    int maxTrisPerChunk;///叶子节点最多包含的三角形数
}

另外recast在实现的时候还对树的存储方式进行了优化,即用一维数组+节点记录偏移值来实现树形结构(类似于线段树的非指针实现)
首先建树和查找都是dfs的顺序,所以在生成节点时按dfs顺序依次向数组里插入就可以了,另外当发现一颗子树中没有与目标点相交的几何体,需要立即跨过这颗子树下的所有节点,
所以在非叶子节点上(即子树的根节点)会记录子树节点总个数,这样遍历时只要加上这个值作为偏移,就能立即跳过这棵子树

最后的实现其实就很简单了(如果你经常写树形数据结构的话)
1.计算所有三角形在xz平面投影的包围盒,并记录与初始三角形的对应关系,放入BoundsItem* items数组中

2.递归建树
对于当前区域,创建一个节点,并生成一个包含区域内所有三角形的包围盒
a.若当前区域内的三角形数量已经不超过允许包含的数量(trisPerChunk),则表示到达了递归边界
b.否则取一条较长的轴,将所有三角形按轴坐标排序后,作一条垂直于轴的分割线,使得分割线分成的两个区域内的三角形个数相等,然后继续递归处理两个区域

你可能感兴趣的:(从零开始学习导航网格#5 rcChunkyTriMesh与BVH)