AABBTree概念简介
AABB 树(Axis-Aligned Bounding Box Tree)是一种在计算机图形学、计算机游戏、机器人运动规划、碰撞检测等领域广泛应用的数据结构,用于高效地处理几何对象之间的空间关系,尤其是碰撞检测方面。
轴对齐包围盒(AABB)
AABB是一个简单的几何概念,它是一个长方体(在二维空间中是矩形),其各边分别与坐标轴平行。对于一组几何对象(如三维模型中的多个三角形面片组成的物体),可以找到一个刚好能包围这些对象的 AABB,这个 AABB 能简单且快速地描述这些对象在空间中的大致位置和范围。

AABB 树
AABB 树的结构:AABB 树是一种二叉树结构,树中的每个节点都对应一个 AABB。叶子节点的 AABB 通常直接包围着场景中的一个基本几何对象(比如一个三角形、一个球体等具体的图形元素),而内部节点的 AABB 则包围着其所有子节点对应的 AABB 所包含的所有几何对象。
下面包含以三角形为基本单位的AABBTree

UE5 Geometry库的AABBTree--TMeshAABBTree3
UE5几何库的AABBTree就是传统的AABB树, 主要用于加速查询最近点,最近三角形,RayHit三角形结果等。
TMeshAABBTree3类图

TMeshAABBTree3构建AABBTree的流程
UE5几何框架的AABBTree构建和常见的AABBTree基本没差别,都是从根节点逐步遍历所有三角形的中心点,然后按照坐标轴(X,Y,Z)进行Middle划分成两个AABB树节点。依次递归,最终求出叶子节点(叶子节点只包含三角形数据). 总体上实现不错的点在于用数组作为树存储,对数据访问Cache比较友好。
[1]遍历Mesh的有效三角形数组和三角形重心数组
cpp
TArray<int32> Triangles;
TArray<FVector3d> Centers;
三角形这里存储的是在DynamicMesh的TriangeIndex
[2]以上面的三角形数组和重心数组为根数据开始进行递归构建
以所有三角形的重心点以某条坐标轴(X或者Y或者Z)求出Middle点, 然后对三角形数组和重心数组进行划分, 小于Middle点为左节点,大于Middle点为右节点. 这里会对三角形数组和重心数组进行重新排序,并求出数组确定一个划分点SplitIndex.(n0)


虽然示意图我画的划分轴是X轴。但这里必须注意的点是划分轴可能是X,Y, Z轴三个维度中的一种,树节点划分空间轴是X还是Y或者Z取决于树节点深度
划分轴 = 树节点深度 % 3.

[3]空间划分后的左右三角形和三角形重心数据作为左右节点按照第二个流程递归进行划分下去

[4]叶子节点的终结条件
这里叶子终结条件为不超过TopDownLeafMaxTriCount个三角形,TopDownLeafMaxTriCount默认为3


叶子节点存储多个三角形的AABB和三角形索引。
[5]非叶子节点和叶子节点的存储和索引
在构建中, 非叶子节点和叶子节点分开存储。
cpp
FBoxesSet Tris; // 叶子节点
FBoxesSet Nodes; // 非叶子节点
FBoxesSet本质就是二叉树结构的数组索引表

叶子节点

非叶子节点

这里比较注意的是叶子节点在IndexList的索引是负数
-(IBox + 1). 这里的IBox为叶子存储表的BoxIndex.

[6]合并非叶子节点列表和叶子节点列表
上述的两个列表合并
cpp
FBoxesSet Tris; // 叶子节点
FBoxesSet Nodes; // 非叶子节点
将叶子节点Appned到叶子节点列表的尾巴。偏移长度为叶子节点列表的数量LeafNum。
(Box+ BoxToIndex + IndexList)三个Append到叶子节点列表,相应的索引数值(BoxToIndex + IndexList + RootNodeIndex)得增加LeafNum。

这里的TriangleEnd是 在IndexList的分水岭。TriangleEnd之前是存储叶子节点的三角形索引, 之后是存储非叶子节点的ChildIndex.
TMeshAABBTree3查询最近的流程
某个点P的最近三角形

设置一个最大距离MaxDistance, 从根节点求P和节点AABB的距离,不断缩小距离,最终递归到叶子节点,从叶子节点中遍历所有三角形,求点和每个三角形的距离, 比较得到最近三角形。

算法复杂度:O(log(n)), n为三角形总数量.
某个点P的最近点(最近三角形的最近点)

由上面求出最近三角形,并进一步求出最近三角形上和点P距离最近的点。

算法复杂度:O(log(n)), n为三角形总数量.
某个点P的最近顶点

有着上面"最近点的"前车之鉴,很多人会误以为"最近顶点" 就是最近的三角形的最近顶点。实际上,"最近顶点"应该是找到最近AABB的叶子节点, 然后遍历叶子节点的所有三角形的所有顶点才得到最近顶点。

算法复杂度:O(log(n)), n为三角形总数量.
TMeshAABBTree3 RayHit的流程
RayHit第一个三角形

总体上和最近三角形思路一致,唯一比较大区别是"最近三角形"是非叶子节点求点和AABB最近距离->叶子节点求点和所有三角形的最近距离。而"RayHit"是求射线和非叶子节点的AABB Hit 的最小距离,-->射线和叶子节点的所有三角形 Hit的最小距离。
算法复杂度:O(log(n)), n为三角形总数量.
RayHit多个三角形
总体上和"RayHit第一个三角形", 区别在于这里并不求出最近的Hit结果。只要被射线击中并且距离在MaxHitDistane范围内,都加入击中结果列表里。


算法复杂度:O(log(n)), n为三角形总数量.
TMeshAABBTree3使用代码示例
最近查询
cpp
// 找到离某个点最近的三角形. 并得到点和三角形之间的距离
const FVector3d Point = FVector3d::Zero();
double Distance = 0.0f;
int32 NearestTriangleId = ABTree.FindNearestTriangle(Point, Distance);
if(NearestTriangleId >= 0)
{
}
// 找到离某个点 Mesh上最近的点(非顶点)
FVector3d NearPoint = ABTree.FindNearestPoint(Point);
// 找到离某个点 Mesh上最近的顶点
int32 NearestVertexId = ABTree.FindNearestVertex(Point, Distance);
RayHit查询
cpp
// ------------------ Ray Hit相关--------------------
// 找到某条射线击中的第一个三角形(返回三角形ID)
FRay Ray = FRay(FVector3d(0.0, 0.0, 0.0), FVector3d(1.0, 1.0, 1.0));
int32 HitTriangleId = ABTree.FindNearestHitTriangle(Ray);
// 找到某条射线击中的第一个三角形(返回三角形ID和距离)
int32 HitTriangleId2 = -1;
double HitDistance = 0.0f;
ABTree.FindNearestHitTriangle(Ray,HitDistance, HitTriangleId2);
// 找到某条射线击中的第一个三角形(返回三角形ID和距离, 重心坐标)
FVector3d FindBaryCoords = FVector3d::Zero();
int32 HitTriangleId3 = -1;
double HitDistance3 = 0.0f;
ABTree.FindNearestHitTriangle(Ray, HitDistance3, HitTriangleId3, FindBaryCoords);
// 找到某条射线击中的所有三角形
TArray<MeshIntersection::FHitIntersectionResult> OutHits;
ABTree.FindAllHitTriangles(Ray, OutHits);
参考资料
[1]MeshAABBTree3.h
[2]豆包--AABBTree