1、介绍
AABB树组件提供了一种静态数据结构和算法,用于对有限的三维几何对象集进行高效的交集和距离查询。可以查询数据结构中存储的几何对象集,以进行交集检测、交集计算和距离计算。
交集查询可以是任何类型的,只要在traits类中实现了相应的交集谓词和构造函数。距离查询仅限于点查询。交集查询的例子包括针对三角形集的线对象(射线、线、线段),或针对线段集的平面对象(平面、三角形)。距离查询的一个例子包括从点查询中找到最接近三角形集的点。
请注意,该组件不适合查找所有相交对象对的问题。我们参考了组件“dD等向性盒的相交序列”,它可以找到所有相交的等向性盒。
AABB树数据结构将几何数据的迭代器范围作为输入,然后将其转换为基元。从这些基元中构建轴对齐边界框(AABB)的层次结构,并用于加速交集和距离查询。每个基元都可以访问一个输入几何对象(所谓的基准)和一个该对象的参考id。一个典型的基元将一个3D三角形作为基准,将多面体的面柄作为id。每个交集查询都可以返回交集对象(例如,用于射线查询的3D点或线段)以及交集基元的id(这里为面柄)。同样,每个距离查询可以返回点查询的最接近点以及最接近基元的id。
左图:机械零件的曲面三角形网格。右图:建造了AABB树。
2、接口
该组件的主要入口点是AABB_tree类,它表示一个静态的AABB树,由一个迭代器范围的几何数据构建而成。一旦实例化,就可以对AABB树进行交集和距离查询。
交点。例如,假设树中包含三角形图元。可以以各种方式查询树与线对象(射线、线段或直线)的交点。我们将不构造任何交点对象的交点测试与构造交点对象的交点测试区分开来。
测试:
函数AABB_tree::do_intersect()测试输入基元是否与查询相交。此函数很快,因为它只涉及谓词,并在第一次遇到交集后停止。
函数AABB_tree::number_of_intersected_ primitives() 计算所有相交图元的数量。
函数AABB_tree::all_intersected_ primitives()枚举所有相交的基元id,而不构造相应的相交对象。
函数AABB_tree::any_intersected_primitive() 返回第一个遇到的相交基本体 ID(如果有的话),而不构造相应的相交对象,并在第一次遇到相交后停止。请注意,树的遍历顺序是这样的,首先并不指查询中相交的任何特定顺序。
函数AABB_tree::first_intersected_primitive() 返回最接近射线源的相应相交对象的相交基本体 ID(如果有)。
数据结构结构:
函数AABB_tree::all_intersections() 检测并构造与输入图元相交的所有对象。
函数AABB_tree::any_intersection()检测并构造第一个遇到的交点,并构造相应的对象。这个函数很快,因为它在第一次遇到交点后就会停止。
函数AABB_tree::first_intersection()检测并构造最接近射线源的交点对象。
距离AABB树通过函数AABB_tree::closest_point()计算给定点查询与输入图元的最近点。
此外,它还可以通过函数AABB_tree::closest_point_and_primitive()计算给定点查询与最接近图元的id,即实现与点查询最小距离的图元的id。
AABB树使用二级搜索结构来加速距离查询。在第一次距离计算之前,用户应通过调用AABB_tree::accelerate_distance_queries()来请求构建该二级结构。默认情况下不生成此数据结构,因为它仅用于距离计算。
警告:不建议在AABB树中使用退化基元,因为特征类的底层谓词和构造可能无法处理它们。例如,如果使用CGAL库中的CGAL::AABB_traits和CGAL::Kernel,则在AABB树中使用退化三角形或线段会导致未定义的行为或崩溃。
3、性能
我们提供了一些AABB树包含一组多面体三角形面的性能数据。我们测量了树构造时间、内存占用和各种交集和距离查询的每秒查询次数。使用的机器是运行Windows XP64的PC,配有英特尔CPU酷睿2至尊版,频率为3.06 GHz,内存为4GB。默认使用的内核是Simple_cartesian<double>(我们实验中最快的)。该程序已使用Visual C ++ 2005编译器编译,并使用了O2选项,以最大限度地提高速度。
3.1、构造
为基准测试树构造而选择的表面三角形网格是图2所示的结点模型(14,400个三角形)。我们测量了该模型以及通过循环细分方案细分的三个更密集版本的树构造时间(AABB树单独和AABB树与内部KD树),该方案将三角形数量乘以四。
Number of triangles | Construction (in ms) | Construction with internal KD-tree (in ms) |
---|---|---|
14,400 | 156 | 157 |
57,600 | 328 | 328 |
230,400 | 1,141 | 1,437 |
921,600 | 4,813 | 5,953 |
3.2、内存
当使用多面体三角面片基元(在AABB_polyhedron_triangle_primitive.h中定义)时,AABB树每基元占用大约61个字节(不构建内部KD树)。当使用每个基元一个参考点构建内部KD树时(调用函数AABB_tree::accelerate_distance_queries()时的默认模式),每基元增加到大约150个字节。请注意,多面体三角面片基元仅存储一个面句柄作为基元id,并从内部存储的面句柄动态计算3D三角形。当在基元中显式存储3D三角形时,树每基元占用大约140个字节而不是60个字节(不构建内部KD树)。
下表提供了三角形数量增加时内存占用量的顺序(单位为 MB)。由于用于加速距离查询的内部 KD 树占据了大部分内存,我们建议为大型模型指定更少的参考点(均匀分布),以通过函数 AABB_tree::accelerate_distance_queries() 构建内部 KD 树,该函数将迭代器范围作为输入。
Number of triangles | AABB tree (in MBytes) | AABB tree with internal KD-tree (in MBytes) |
---|---|---|
18,400 | 1.10 | 2.76 |
102,400 | 6.33 | 14.73 |
1,022,400 | 59.56 | 151.31 |
1,822,400 | 108.34 | 291.84 |
3.3、交点
下表测量了结点网格模型的 14400 个三角形版本上每秒的相交查询数量,用于射线、线、线段和平面查询。每个射线查询是通过在网格边界框内选择一个随机源点和随机向量生成的。线或线段查询是通过在边界框内选择两个随机点生成的。平面查询是通过在边界框内选择一个随机点和随机法向量生成的。请注意,平面查询通常会与输入曲面网格的许多三角形相交。这解释了枚举所有交点的相交函数的低性能数字。
Function | Segment | Ray | Line | Plane |
---|---|---|---|---|
AABB_tree::do_intersect() | 187,868 | 185,649 | 206,096 | 377,969 |
AABB_tree::any_intersected_primitive() | 190,684 | 190,027 | 208,941 | 360,337 |
AABB_tree::any_intersection() | 147,468 | 143,230 | 148,235 | 229,336 |
AABB_tree::number_of_intersected_primitives() | 64,389 | 52,943 | 54,559 | 7,906 |
AABB_tree::all_intersected_primitives() | 65,553 | 54,838 | 53,183 | 5,693 |
AABB_tree::all_intersections() | 46,507 | 38,471 | 36,374 | 2,644 |
下图的曲线绘制了每秒查询次数(此处为具有随机分段查询的AABB_tree::all_interctions()函数)与节点三角形曲面网格的输入三角形数量的关系图。
每秒针对14K(显示)、57K、230K和921K三角形的结模型的三角形数的查询数。我们使用在边界框内随机选择的分段查询来调用all_interactions()函数。
下表测量了每秒针对几个内核的AABB_tree::all_interactions()查询数。我们使用节点网格模型的14400三角形版本进行随机分段查询。请注意Simple_cartesian内核是如何比笛卡尔内核快得多的。
Kernel | Queries/s (all_intersections() with segment queries) |
---|---|
Simple_cartesian<double> | 46,507 |
Simple_cartesian<float> | 43,187 |
Cartesian<double> | 5,335 |
Cartesian<float> | 5,522 |
Exact_predicates_inexact_constructions_kernel | 18,411 |
3.4、距离
为基准距离选择的曲面三角形网格再次是通过循环细分获得的四个递增分辨率的结模型。在下表中,我们首先测量了树的构建时间(包括在我们的实验中用于将距离查询加速高达一个数量级的内部KD树数据结构的构建)。然后,我们测量三种类型的距离查询(AABB_tree::closest_point()、AABB_tee::squared_distance()和AABB_tree::closest_proint_and_pritive())每秒从边界框内随机选择的点查询中的查询数。
Nb triangles | Construction (ms) | Closest_point() | Squared_distance() | Closest_point_and_primitive() |
---|---|---|---|---|
14,400 | 157.000 | 45,132 | 45,626 | 45,770 |
57,600 | 328.000 | 21,589 | 21,312 | 21,137 |
230,400 | 1.437 | 11,063 | 10,962 | 11,086 |
921,600 | 5.953 | 5,636 | 5,722 | 5,703 |
3.5、总结
上述实验既不详尽,也不具有决定性,因为我们选择了一个特定案例,其中输入基元是三角形表面多面体的面。然而,我们现在提供了一些关于如何使用AABB树并获得满意性能的一般观察和建议。虽然树构造时间和内存占用在我们的实验中根据输入表面三角形网格没有太大波动,但查询数量的性能根据标准的复杂组合而变化很大:内核类型、输入基元数量、基元在空间中的分布、查询函数类型、查询类型和查询空间位置。
内核:事实证明,CGAL内核的类型主导了最终的执行时间,使用用双精度数字类型参数化的Simple_cartesian内核可以获得最佳性能。在交集和距离执行时间至关重要的应用中,可以将该内核用于AABB树,并结合更健壮的内核用于主要数据结构。
基元:虽然输入基元的数量在最终性能中起着明显的作用,但为了获得平衡良好的AABB树,它们在空间中的分布至少同样重要。理想情况下,基元必须在空间中均匀分布,并且必须尽可能避免跨越树根节点边界的长基元。在构建树之前,将这些长基元分割成更小的基元通常是有益的,例如,通过递归最长边二等分三角形曲面网格。
函数:查询的函数类型起着另一个重要作用。显然,列出所有交点的穷举函数比在第一个交点后停止的函数慢。在这些函数中,只调用交点测试的函数(AABB_tree::do_intersect()、AABB_tree::number_of_intersected_ primitives()、AABB_tree::any_intersected_primitive()、AABB_tree::all_intersected_ primitives())比显式构造交点的函数(AABB_tree::any_intersection()和AABB_tree::all_intersections())快。
查询:查询:查询的类型(例如,上面使用的线、射线、线段或平面)起着另一个作用,与函数类型(详尽与否,以及是否构造交点)密切相关。当需要所有交点构造时,最终的执行时间在很大程度上取决于一般交点对象的复杂性。例如,平面查询通常将曲面三角形网格分割成许多线段,而线段查询通常将曲面三角形网格分割成少数点。最后,查询在空间中的位置在性能上起着明显的作用,特别是对于距离查询。假设通过函数AABB_tree::accelerate_distance_queries()构建内部KD树,最好指定一个已经接近曲面三角形网格的查询点,以便查询只遍历树的少数AABB。然而,对于大量原始数据(在我们的实验中大于2M个面),我们注意到在构建KD树时使用所有参考点是不必要的(有时甚至更慢)。在这些情况下,我们建议通过函数AABB_tree::accelerate_distance_queries()指定更少的参考点(通常不超过100K),均匀分布在输入图元上。
4、实施
AABB树构建是通过计算整个输入图元的AABB来初始化的。然后,所有图元沿着这个长方体的最长坐标轴进行排序,并将图元分为两个大小相等的集合。这个过程被递归应用,直到AABB包含单个图元。该树是无叶的,如OPCODE 所示。交集查询通过在遍历过程中仅计算AABB的交集测试,并在遍历结束时(在树的叶子中)计算输入图元的交集测试来遍历树。
参考ID在内部不使用,仅供AABB树在提供给用户的结果中引用基本体。因此,虽然在大多数情况下,每个参考ID对应一个唯一的基元,但这并不是组件的要求。这样,用户可以将这些参考ID用作标签,每个标签由多个几何对象共享。
查询点q和输入图元之间的距离查询被转化为以q为中心的球查询。球穿过树,同时递归查询与AABB的交点,并计算树叶上查询点与输入图元的最接近点p。然后,在树的其余递归遍历中,球半径缩小到p和q之间的距离。通过将初始球半径设置为一个小值,仍然保证与输入图元的交集,从而实现效率。这是通过构造内部次级数据结构来实现的,该结构在遍历开始时为算法提供了很好的提示(默认情况下完成)。调用do_not_accelerate_distance_queries()将禁用此内部次级数据结构的构造和使用。