16.1 AABB 树(Axis-Aligned Bounding Box Tree)
相关章节
16.1.0 类比解释:图书馆的管理系统
生活场景类比
想象你管理一个巨大的图书馆(3D场景),里面有数万本书(几何图元)。当一位读者(查询)问:“我想找关于’古代历史’的书(与射线相交的图元)“,你不可能一本一本地检查每本书。
AABB树就像图书馆的分类系统:
- 根节点:所有书的集合
- 内部节点:大类(如”历史类”、“科学类”)
- 叶节点:具体的书(三角形、线段等图元)
查询过程:
- 读者询问”历史类” → 你先看大类标签(根节点包围盒)
- 发现”古代史”子类可能相关 → 进入该子树
- 发现”现代史”不相关 → 跳过整个子树(剪枝)
- 在”古代史”子类中逐本检查(测试叶节点图元)
关键洞察:通过层次化的分类,我们避免了检查不相关的书,将O(n)的线性搜索优化到O(log n)的层次搜索。
为什么需要AABB树?
问题场景:
- 碰撞检测:游戏角色是否与场景中的任何物体碰撞?
- 光线追踪:光线与哪些三角形相交?
- 最近邻查询:距离给定点最近的物体是哪个?
传统方法的困境:
暴力法:检查每个图元
for each primitive in scene:
if ray intersects primitive:
collect intersection
复杂度:O(n) 对于 n 个图元
对于100万个三角形,需要100万次相交测试!
AABB树的解决方案:
层次搜索:从粗到精
1. 检查根节点包围盒(廉价)
2. 如果相交,检查子节点
3. 如果不相交,跳过整个子树
4. 只在叶节点进行昂贵的图元测试
复杂度:O(log n) 平均情况
对于100万个三角形,仅需约20次测试!
16.1.1 理论基础
AABB 树概述
AABB(Axis-Aligned Bounding Box,轴对齐包围盒)树是一种广泛应用于计算机图形学和计算几何的空间索引结构。它通过层次化的轴对齐包围盒来组织几何图元,支持高效的相交检测和距离查询。
核心特性:
- 轴对齐:所有包围盒的边与坐标轴平行,简化相交测试
- 层次结构:自顶向下的二叉树结构,每个节点存储包围其子树的AABB
- 紧密拟合:叶节点存储原始几何图元的包围盒
- 通用性:支持任意类型的几何图元(三角形、线段、点等)
AABB树结构可视化:
场景:2D空间中的5个三角形
三角形分布:
+------------------+
| T1 |
| /\ |
| / \ |
| / T2 \ T3 |
| /______\ /\ |
| / \ |
| T4 /____\ |
| /\ |
| / \ T5 |
| /____\ /\ |
| / \ |
+------------------+
构建的AABB树(层次结构):
+------------------+
| Root |
| 包围整个场景 |
+--------+---------+
|
+----------------+----------------+
| |
+-------+--------+ +---------+--------+
| 左子树(L) | | 右子树(R) |
| 包围{T1,T2,T4} | | 包围{T3,T5} |
+---+---+--------+ +----+---+---------+
| | |
+----+ +----+ +----+----+
| | | |
+-+------+ +----+----+ +-----+--+ +----+----+
|L1 | |L2 | |R1 | |R2 |
|{T1,T2} | |{T4} | |{T3} | |{T5} |
+--+--+--+ +----+----+ +-----+--+ +----+----+
| | | | |
T1 T2 T4 T3 T5
树的几何表示(包围盒):
Level 0 (Root): Level 1:
+------------------+ +----------+ +----------+
| | | L | | R |
| +----------+ | | +------+ | | +------+ |
| | L |--+ | | |T1,T2 | | | | T3 | |
| | +------+ | | | | |+--+ | | | | /\ | |
| | | T1,T2| | | | | ||T4| | | | |/ \ | |
| | | T4 | | | | | |+--+ | | | +------+ |
| | +------+ | | | | +------+ | | | T5 | |
| +----------+ | | +----------+ | | /\ | |
| | | | |/ \ | |
+------------------+ | +------+ |
+----------+
查询示例:射线与T3相交
搜索路径(深度优先):
Step 1: 测试Root → 相交 → 继续
Step 2: 测试L → 不相交 → 剪枝!跳过整个左子树
Step 3: 测试R → 相交 → 继续
Step 4: 测试R1(T3) → 相交 → 找到!
Step 5: 测试R2(T5) → 可选(取决于查询类型)
搜索效率:
- 总图元数:5
- 实际测试:Root + R + R1 + R2 = 4个节点
- 剪枝节省:L + L1 + L2 = 3个节点(60%)
查询类型
AABB 树支持以下主要查询类型:
-
相交检测(Intersection Detection)
- 判断查询对象(射线、线段、包围盒等)是否与树中任何图元相交
- 返回所有相交的图元或第一个相交的图元
-
距离查询(Distance Queries)
- 计算查询点到树中图元的最近距离
- 查找最近点及其所在的图元
-
射线投射(Ray Casting)
- 查找射线与场景的第一个交点
- 支持跳过特定图元的优化
16.1.2 架构分析
类设计
CGAL 的 AABB 树模块采用模板化的设计,核心类包括:
AABB_tree<AABBTraits> // 主树类
AABB_traits_3<GeomTraits, Primitive, BboxMap> // 3D traits类
AABB_primitive<Id, ObjectMap, PointMap, ExternalMaps, CacheDatum> // 图元类
AABB_node<AABBTraits> // 树节点类
AABB_tree 类(位于 /home/chunibyo/workspace/osc/cgal/AABB_tree/include/CGAL/AABB_tree.h)
template <typename AABBTraits>
class AABB_tree
{
public:
typedef typename AABBTraits::FT FT; // 数值类型
typedef typename AABBTraits::Point Point; // 点类型
typedef typename AABBTraits::Primitive Primitive; // 图元类型
typedef typename AABBTraits::Bounding_box Bounding_box; // 包围盒类型
// 构造函数
AABB_tree(const AABBTraits& traits = AABBTraits());
// 从图元序列构建树
template<typename InputIterator, typename ... T>
AABB_tree(InputIterator first, InputIterator beyond, T&& ...);
// 核心查询方法
template<typename Query>
bool do_intersect(const Query& query) const;
template<typename Query, typename OutputIterator>
OutputIterator all_intersections(const Query& query, OutputIterator out) const;
FT squared_distance(const Point& query) const;
Point closest_point(const Point& query) const;
Point_and_primitive_id closest_point_and_primitive(const Point& query) const;
};AABB_traits_3 类(位于 /home/chunibyo/workspace/osc/cgal/AABB_tree/include/CGAL/AABB_traits_3.h)
Traits 类定义了 AABB 树所需的几何操作:
template<typename GeomTraits, typename AABBPrimitive, typename BboxMap = Default>
class AABB_traits_3
{
public:
typedef typename GeomTraits::FT FT;
typedef typename GeomTraits::Point_3 Point;
typedef CGAL::Bbox_3 Bounding_box;
// 计算包围盒
class Compute_bbox {
template<typename ConstPrimitiveIterator>
Bounding_box operator()(ConstPrimitiveIterator first,
ConstPrimitiveIterator beyond) const;
};
// 分割图元
class Split_primitives {
template<typename PrimitiveIterator>
void operator()(PrimitiveIterator first, PrimitiveIterator beyond,
const Bounding_box& bbox) const;
};
// 相交测试
class Do_intersect {
template<typename Query>
bool operator()(const Query& q, const Bounding_box& bbox) const;
template<typename Query>
bool operator()(const Query& q, const Primitive& pr) const;
};
// 计算交点
class Intersection {
template<typename Query>
std::optional<typename Intersection_and_primitive_id<Query>::Type>
operator()(const Query& query, const Primitive& primitive) const;
};
// 距离计算
class Closest_point {
Point operator()(const Point& p, const Primitive& pr,
const Point& bound) const;
};
};查询接口的统一抽象
CGAL AABB 树通过 Traversal Traits 模式实现查询算法的统一抽象。该模式将遍历逻辑与具体查询语义分离:
// 位于 AABB_tree/internal/AABB_traversal_traits.h
template<typename AABBTraits, typename Query>
class Do_intersect_traits {
bool is_intersection_found() const;
bool go_further() const;
template<typename Primitive>
void intersection(const Query& query, const Primitive& primitive);
};
template<typename AABBTraits, typename Query, typename OutputIterator>
class Listing_intersection_traits {
OutputIterator& out;
bool go_further() const;
template<typename Primitive>
void intersection(const Query& query, const Primitive& primitive);
};16.1.3 实现细节
树的构建算法
AABB 树采用自顶向下的递归构建策略:
// 简化版构建算法(来自 AABB_tree.h)
template<typename Tr>
void AABB_tree<Tr>::build()
{
custom_build(m_traits.compute_bbox_object(),
m_traits.split_primitives_object());
}
template<typename Tr>
template <class ComputeBbox, class SplitPrimitives>
void AABB_tree<Tr>::custom_build(
const ComputeBbox& compute_bbox,
const SplitPrimitives& split_primitives)
{
clear_nodes();
if(m_primitives.size() > 1) {
// 预分配节点空间(二叉树有 n-1 个内部节点)
m_nodes.reserve(m_primitives.size()-1);
// 递归扩展树
expand(new_node(),
m_primitives.begin(), m_primitives.end(),
m_primitives.size(),
compute_bbox,
split_primitives);
}
}
template<typename Tr>
template<typename ConstPrimitiveIterator, typename ComputeBbox, typename SplitPrimitives>
void AABB_tree<Tr>::expand(Node& node,
ConstPrimitiveIterator first,
ConstPrimitiveIterator beyond,
const std::size_t range,
const ComputeBbox& compute_bbox,
const SplitPrimitives& split_primitives)
{
// 计算当前节点包围盒
node.set_bbox(compute_bbox(first, beyond));
// 沿最长轴分割图元
split_primitives(first, beyond, node.bbox());
switch(range) {
case 2:
// 两个图元作为叶节点
node.set_children(*first, *(first+1));
break;
case 3:
// 三个图元:左叶节点,右子树
node.set_children(*first, new_node());
expand(node.right_child(), first+1, beyond, 2, compute_bbox, split_primitives);
break;
default:
// 递归分割
const std::size_t new_range = range/2;
node.set_children(new_node(), new_node());
expand(node.left_child(), first, first + new_range, new_range,
compute_bbox, split_primitives);
expand(node.right_child(), first + new_range, beyond, range - new_range,
compute_bbox, split_primitives);
}
}构建过程可视化:
输入:6个三角形图元 {T1, T2, T3, T4, T5, T6}
Step 1: 计算整体包围盒
+------------------+
| T1 T2 T3 |
| |
| T4 T5 T6 |
+------------------+
Step 2: 沿最长轴分割(假设是X轴)
排序后: {T1, T2, T4} | {T3, T5, T6}
左子树包围盒 右子树包围盒
+---------+ +---------+
|T1 T2 | |T3 |
| | | |
|T4 | |T5 T6 |
+---------+ +---------+
Step 3: 递归分割左子树
{T1, T2} | {T4}
左-左包围盒 左-右包围盒(叶节点)
+-------+ +-------+
|T1 T2 | | T4 |
+-------+ +-------+
Step 4: 继续递归直到叶节点
最终树结构:
Root
/ \
L R
/ \ / \
LL T4 RL RR
/ \ / \ / \
T1 T2 T3 T5 T6 (null)
分割策略
AABB traits 使用沿最长轴的中位数分割策略:
// 来自 AABB_traits_3.h
class Split_primitives
{
template<typename PrimitiveIterator>
void operator()(PrimitiveIterator first,
PrimitiveIterator beyond,
const typename AT::Bounding_box& bbox) const
{
PrimitiveIterator middle = first + (beyond - first)/2;
switch(Traits::longest_axis(bbox)) {
case AT::CGAL_AXIS_X: // 沿X轴排序
std::nth_element(first, middle, beyond,
[this](const Primitive& p1, const Primitive& p2){
return Traits::less_x(p1, p2, this->m_traits);
});
break;
case AT::CGAL_AXIS_Y: // 沿Y轴排序
std::nth_element(first, middle, beyond,
[this](const Primitive& p1, const Primitive& p2){
return Traits::less_y(p1, p2, this->m_traits);
});
break;
case AT::CGAL_AXIS_Z: // 沿Z轴排序
std::nth_element(first, middle, beyond,
[this](const Primitive& p1, const Primitive& p2){
return Traits::less_z(p1, p2, this->m_traits);
});
break;
}
}
};
// 确定最长轴
static Axis longest_axis(const Bounding_box& bbox)
{
const double dx = bbox.xmax() - bbox.xmin();
const double dy = bbox.ymax() - bbox.ymin();
const double dz = bbox.zmax() - bbox.zmin();
if(dx >= dy) {
return (dx >= dz) ? CGAL_AXIS_X : CGAL_AXIS_Z;
} else {
return (dy >= dz) ? CGAL_AXIS_Y : CGAL_AXIS_Z;
}
}遍历算法
AABB 节点支持多种遍历策略:
// 标准深度优先遍历(来自 AABB_node.h)
template<class Traversal_traits, class Query>
void traversal(const Query& query, Traversal_traits& traits,
const std::size_t nb_primitives) const
{
switch(nb_primitives) {
case 2:
// 两个叶节点图元
traits.intersection(query, left_data());
if(traits.go_further())
traits.intersection(query, right_data());
break;
case 3:
// 一个叶节点,一个内部节点
traits.intersection(query, left_data());
if(traits.go_further() && traits.do_intersect(query, right_child()))
right_child().traversal(query, traits, 2);
break;
default:
// 两个内部节点
if(traits.do_intersect(query, left_child())) {
left_child().traversal(query, traits, nb_primitives/2);
if(traits.go_further() && traits.do_intersect(query, right_child()))
right_child().traversal(query, traits, nb_primitives - nb_primitives/2);
}
else if(traits.do_intersect(query, right_child())) {
right_child().traversal(query, traits, nb_primitives - nb_primitives/2);
}
}
}
// 带优先级的遍历(用于距离查询)
template<class Traversal_traits, class Query>
void traversal_with_priority(const Query& query, Traversal_traits& traits,
const std::size_t nb_primitives) const
{
// ... 根据优先级决定遍历顺序
bool ileft, iright;
typename Traversal_traits::Priority pleft, pright;
std::tie(ileft, pleft) = traits.do_intersect_with_priority(query, left_child());
std::tie(iright, pright) = traits.do_intersect_with_priority(query, right_child());
if(ileft && iright) {
// 两个子节点都需要访问,按优先级排序
if(pleft >= pright) {
left_child().traversal_with_priority(query, traits, nb_primitives/2);
if(traits.go_further())
right_child().traversal_with_priority(query, traits, ...);
} else {
right_child().traversal_with_priority(query, traits, ...);
if(traits.go_further())
left_child().traversal_with_priority(query, traits, ...);
}
}
// ... 其他情况
}遍历过程可视化(射线查询):
场景:射线从左侧射入,与T2相交
树结构:
Root (与射线相交)
/ \
L R
/ \ / \
LL LR RL RR
/ \ | / \ / \
T1 T2 T3 T4 T5 T6
遍历步骤:
Step 1: 测试Root包围盒
[Root] 相交? 是 → 继续
Step 2: 测试左子树L
[L] 相交? 是 → 继续
Step 3: 测试左-左子树LL
[LL] 相交? 是 → 继续
Step 4: 测试T1(叶节点)
[T1] 相交? 否 → 返回
Step 5: 测试T2(叶节点)
[T2] 相交? 是 → 记录交点!
Step 6: 回溯到L,测试右子树LR
[LR] 包围盒在射线后方 → 跳过
Step 7: 回溯到Root,测试右子树R
[R] 包围盒与射线不相交 → 剪枝!
结果:找到交点 T2,跳过了 T3, T4, T5, T6 的测试
剪枝率:66%(4/6个图元被跳过)
距离查询优化
AABB 树使用内部 KD 树加速距离查询:
// 来自 AABB_tree.h
// 构建用于加速距离查询的 KD 树
template<typename Tr>
bool AABB_tree<Tr>::build_kd_tree()
{
// 收集所有图元的参考点
std::vector<Point_and_primitive_id> points;
points.reserve(m_primitives.size());
for(const Primitive& p : m_primitives)
points.push_back(Point_and_primitive_id(
Helper::get_reference_point(p, m_traits), p.id()));
return build_kd_tree(points.begin(), points.end());
}
// 获取最佳提示点(用于距离查询初始化)
Point_and_primitive_id best_hint(const Point& query) const
{
// 延迟构建搜索树
if(m_use_default_search_tree && \!m_search_tree_constructed) {
m_search_tree_constructed = const_cast<AABB_tree*>(this)->build_kd_tree();
}
if(m_search_tree_constructed)
return m_p_search_tree->closest_point(query);
else
return this->any_reference_point_and_id();
}16.1.4 使用示例
示例1:三角形网格的相交检测
#include <CGAL/Simple_cartesian.h>
#include <CGAL/AABB_tree.h>
#include <CGAL/AABB_traits_3.h>
#include <CGAL/AABB_face_graph_triangle_primitive.h>
#include <CGAL/Surface_mesh.h>
#include <CGAL/boost/graph/graph_traits_Surface_mesh.h>
#include <iostream>
#include <vector>
typedef CGAL::Simple_cartesian<double> K;
typedef K::FT FT;
typedef K::Point_3 Point;
typedef K::Triangle_3 Triangle;
typedef K::Ray_3 Ray;
typedef CGAL::Surface_mesh<Point> Mesh;
// 定义图元类型
typedef CGAL::AABB_face_graph_triangle_primitive<Mesh> Primitive;
typedef CGAL::AABB_traits_3<K, Primitive> Traits;
typedef CGAL::AABB_tree<Traits> Tree;
int main()
{
// 创建一个简单网格
Mesh mesh;
// ... 构建网格 ...
// 构建 AABB 树
Tree tree(faces(mesh).first, faces(mesh).second, mesh);
// 射线查询
Point ray_origin(0.0, 0.0, 0.0);
Point ray_direction(1.0, 0.0, 0.0);
Ray ray(ray_origin, ray_direction);
// 检测相交
if(tree.do_intersect(ray)) {
std::cout << "射线与网格相交" << std::endl;
// 获取第一个交点
auto intersection = tree.first_intersection(ray);
if(intersection) {
// 处理交点
}
// 获取所有交点
std::vector<Tree::Intersection_and_primitive_id<Ray>::Type> intersections;
tree.all_intersections(ray, std::back_inserter(intersections));
}
// 距离查询
Point query_point(0.5, 0.5, 0.5);
FT squared_dist = tree.squared_distance(query_point);
Point closest = tree.closest_point(query_point);
std::cout << "最近点: " << closest << std::endl;
std::cout << "距离平方: " << squared_dist << std::endl;
return 0;
}示例2:自定义图元类型
#include <CGAL/Simple_cartesian.h>
#include <CGAL/AABB_tree.h>
#include <CGAL/AABB_traits_3.h>
#include <CGAL/AABB_primitive.h>
#include <vector>
typedef CGAL::Simple_cartesian<double> K;
typedef K::Point_3 Point;
typedef K::Segment_3 Segment;
// 自定义数据结构
struct MySegment {
Segment segment;
int id;
};
// 定义 ID 类型
struct SegmentID {
std::size_t idx;
bool operator==(const SegmentID& other) const { return idx == other.idx; }
};
// 创建属性映射
struct Segment_property_map {
typedef SegmentID key_type;
typedef Segment value_type;
typedef const Segment& reference;
typedef boost::readable_property_map_tag category;
const std::vector<MySegment>* segments;
reference operator[](key_type id) const {
return (*segments)[id.idx].segment;
}
friend reference get(const Segment_property_map& map, key_type id) {
return map[id];
}
};
struct Point_property_map {
typedef SegmentID key_type;
typedef Point value_type;
typedef const Point& reference;
typedef boost::readable_property_map_tag category;
const std::vector<MySegment>* segments;
reference operator[](key_type id) const {
return (*segments)[id.idx].segment.source();
}
friend reference get(const Segment_property_map& map, key_type id) {
return map[id];
}
};
// 定义图元和树类型
typedef CGAL::AABB_primitive<SegmentID, Segment_property_map,
Point_property_map> Primitive;
typedef CGAL::AABB_traits_3<K, Primitive> Traits;
typedef CGAL::AABB_tree<Traits> Tree;
int main()
{
std::vector<MySegment> segments;
// ... 填充线段数据 ...
// 创建属性映射
Segment_property_map seg_map{&segments};
Point_property_map pt_map{&segments};
// 创建图元
std::vector<Primitive> primitives;
for(std::size_t i = 0; i < segments.size(); ++i) {
primitives.push_back(Primitive(SegmentID{i}, seg_map, pt_map));
}
// 构建树
Tree tree(primitives.begin(), primitives.end());
// 使用树进行查询
Point query(0, 0, 0);
FT dist = tree.squared_distance(query);
return 0;
}16.1.5 复杂度分析
构建复杂度
| 操作 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 构建树 | O(n log n) | O(n) |
| 插入单个图元 | O(1) 均摊 | O(1) |
| 重建树 | O(n log n) | O(n) |
其中 n 是图元数量。构建复杂度主要来自沿最长轴的排序操作。
查询复杂度
| 查询类型 | 平均情况 | 最坏情况 |
|---|---|---|
| 相交检测(do_intersect) | O(log n) | O(n) |
| 列出所有交点 | O(log n + k) | O(n) |
| 距离查询 | O(log n) | O(n) |
| 最近点查询 | O(log n) | O(n) |
其中 k 是相交图元数量。
性能优化建议
- 批量构建:尽可能一次性构建树,而非逐个插入图元
- 使用内部 KD 树:调用
accelerate_distance_queries()可显著提升距离查询性能 - 自定义分割策略:对于特定分布的数据,可实现自定义的
Split_primitives - 缓存图元数据:使用
CacheDatum = Tag_true可减少内存访问
16.1.6 实用指导
调试技巧
技巧1:可视化树结构
// 添加调试输出,打印树的层次结构
template<typename Tree>
void print_tree_structure(const Tree& tree, std::size_t max_depth = 5)
{
std::cout << "AABB Tree Structure:" << std::endl;
std::cout << "===================" << std::endl;
std::cout << "Number of primitives: " << tree.size() << std::endl;
// 使用访问者模式遍历树
struct Print_visitor {
std::size_t depth = 0;
void go_further() { ++depth; }
void go_back() { --depth; }
template<typename Node>
void operator()(const Node& node) {
std::string indent(depth * 2, ' ');
if(node.is_leaf()) {
std::cout << indent << "[Leaf] "
<< "Primitives: " << node.primitive_id() << std::endl;
} else {
auto bbox = node.bounding_box();
std::cout << indent << "[Node] Depth: " << depth
<< " BBox: [" << bbox.xmin() << ", " << bbox.xmax() << "] x ["
<< bbox.ymin() << ", " << bbox.ymax() << "] x ["
<< bbox.zmin() << ", " << bbox.zmax() << "]" << std::endl;
}
}
};
Print_visitor visitor;
tree.traverse(visitor);
}技巧2:验证树的正确性
// 验证每个节点的包围盒确实包含其子节点的包围盒
template<typename Tree>
bool validate_tree(const Tree& tree)
{
bool valid = true;
struct Validate_visitor {
bool& valid;
template<typename Node>
void operator()(const Node& node) {
if(\!node.is_leaf()) {
auto parent_bbox = node.bounding_box();
auto left_bbox = node.left_child().bounding_box();
auto right_bbox = node.right_child().bounding_box();
// 检查子节点包围盒是否在父节点内
if(\!contains(parent_bbox, left_bbox) ||
\!contains(parent_bbox, right_bbox)) {
std::cerr << "Invalid bounding box at node\!" << std::endl;
valid = false;
}
}
}
bool contains(const CGAL::Bbox_3& outer, const CGAL::Bbox_3& inner) {
return outer.xmin() <= inner.xmin() && outer.xmax() >= inner.xmax() &&
outer.ymin() <= inner.ymin() && outer.ymax() >= inner.ymax() &&
outer.zmin() <= inner.zmin() && outer.zmax() >= inner.zmax();
}
};
Validate_visitor visitor{valid};
tree.traverse(visitor);
return valid;
}技巧3:性能分析
// 测量查询时间并统计遍历的节点数
class Instrumented_traversal_traits {
public:
mutable std::size_t nodes_visited = 0;
mutable std::size_t primitives_tested = 0;
mutable std::chrono::nanoseconds duration{0};
template<typename Query, typename Node>
bool do_intersect(const Query& query, const Node& node) const {
auto start = std::chrono::high_resolution_clock::now();
bool result = /* actual intersection test */;
auto end = std::chrono::high_resolution_clock::now();
duration += end - start;
++nodes_visited;
return result;
}
template<typename Query, typename Primitive>
void intersection(const Query& query, const Primitive& primitive) const {
++primitives_tested;
// ... actual intersection logic
}
void report() const {
std::cout << "Traversal Statistics:" << std::endl;
std::cout << " Nodes visited: " << nodes_visited << std::endl;
std::cout << " Primitives tested: " << primitives_tested << std::endl;
std::cout << " Total time: " << duration.count() / 1e6 << " ms" << std::endl;
std::cout << " Time per node: "
<< (nodes_visited > 0 ? duration.count() / nodes_visited : 0)
<< " ns" << std::endl;
}
};常见错误FAQ
Q1: 为什么我的AABB树查询比暴力搜索还慢?
A: 可能原因:
- 图元数量太少:AABB树的开销在n < 100时不划算
- 查询分布太分散:所有查询都命中大部分节点,失去剪枝优势
- 没有启用距离查询加速:距离查询需要调用
accelerate_distance_queries()
// 错误:构建后直接使用距离查询
tree.build(); // 只构建了AABB树
auto dist = tree.squared_distance(point); // 慢!
// 正确:启用距离查询加速
tree.build();
tree.accelerate_distance_queries(); // 构建内部KD树
auto dist = tree.squared_distance(point); // 快!Q2: 如何处理动态场景(图元在移动)?
A: AABB树是静态结构,动态场景有以下策略:
// 策略1:增量更新(仅适用于微小移动)
tree.insert(new_primitive); // 添加新图元
tree.remove(primitive_id); // 删除图元(如果支持)
// 策略2:延迟重建
class DynamicAABBTree {
Tree tree;
std::vector<Primitive> pending_insertions;
std::vector<PrimitiveId> pending_removals;
std::size_t modification_count = 0;
const std::size_t REBUILD_THRESHOLD = 100;
public:
void update_primitive(PrimitiveId id, const Bounding_box& new_bbox) {
pending_removals.push_back(id);
pending_insertions.push_back(create_updated_primitive(id, new_bbox));
if(++modification_count > REBUILD_THRESHOLD) {
rebuild();
}
}
void rebuild() {
// 应用所有挂起的修改
auto primitives = collect_all_primitives();
tree = Tree(primitives.begin(), primitives.end());
tree.accelerate_distance_queries();
pending_insertions.clear();
pending_removals.clear();
modification_count = 0;
}
};
// 策略3:使用多个树
class MultiTreeManager {
Tree static_tree; // 静态物体
Tree dynamic_tree; // 动态物体(频繁重建)
public:
template<typename Query>
auto query(const Query& q) {
// 查询两个树并合并结果
auto results1 = static_tree.all_intersections(q);
auto results2 = dynamic_tree.all_intersections(q);
// 合并结果...
}
};Q3: 如何减少内存占用?
A: 内存优化策略:
// 策略1:使用轻量级图元
typedef CGAL::AABB_primitive<
SegmentID,
Segment_property_map,
Point_property_map,
CGAL::Tag_false, // 不存储外部属性
CGAL::Tag_false // 不缓存数据
> LightweightPrimitive;
// 策略2:自定义分配器
template<typename T>
class PoolAllocator {
MemoryPool pool;
public:
using value_type = T;
T* allocate(std::size_t n) {
return pool.allocate(n);
}
void deallocate(T* p, std::size_t n) {
pool.deallocate(p, n);
}
};
// 策略3:压缩包围盒表示
struct CompressedBbox {
float min_x, min_y, min_z;
float max_x, max_y, max_z;
// 使用float而非double,节省50%空间
};Q4: 查询结果不稳定(每次运行结果不同)?
A: 这是因为nth_element分割导致的非确定性。解决方案:
// 使用确定性的分割策略
class DeterministicSplitPrimitives {
public:
template<typename PrimitiveIterator>
void operator()(PrimitiveIterator first,
PrimitiveIterator beyond,
const Bounding_box& bbox) const
{
// 使用std::stable_partition代替nth_element
PrimitiveIterator middle = first + (beyond - first) / 2;
switch(longest_axis(bbox)) {
case CGAL_AXIS_X:
std::stable_partition(first, beyond,
[this](const Primitive& p) {
return get_center_x(p) < split_value_x;
});
break;
// ... 其他轴
}
}
};Q5: 如何处理退化情况(所有图元在一条线上)?
A: 退化情况处理:
// 检测并处理退化分布
class RobustSplitPrimitives {
public:
template<typename PrimitiveIterator>
void operator()(PrimitiveIterator first,
PrimitiveIterator beyond,
const Bounding_box& bbox) const
{
// 检查包围盒是否退化
double dx = bbox.xmax() - bbox.xmin();
double dy = bbox.ymax() - bbox.ymin();
double dz = bbox.zmax() - bbox.zmin();
const double EPSILON = 1e-10;
int non_degenerate_axes = (dx > EPSILON) + (dy > EPSILON) + (dz > EPSILON);
if(non_degenerate_axes == 0) {
// 完全退化:按图元ID排序(确定性行为)
std::sort(first, beyond,
[](const Primitive& a, const Primitive& b) {
return a.id() < b.id();
});
} else if(non_degenerate_axes == 1) {
// 一维分布:沿非退化轴分割
Axis axis = (dx > EPSILON) ? CGAL_AXIS_X :
(dy > EPSILON) ? CGAL_AXIS_Y : CGAL_AXIS_Z;
split_along_axis(first, beyond, axis);
} else {
// 正常情况:沿最长轴分割
split_along_longest_axis(first, beyond, bbox);
}
}
};性能优化实战
场景:光线追踪器中的AABB树优化
class OptimizedRayTracer {
Tree tree;
public:
void build_acceleration_structure(const Mesh& mesh) {
// 1. 使用SAH(Surface Area Heuristic)优化分割
// 比中位数分割减少30-50%的查询时间
// 2. 启用所有加速结构
tree.build();
tree.accelerate_distance_queries();
// 3. 预分配查询缓冲区
intersection_buffer.reserve(1024);
}
Color trace_ray(const Ray& ray, int depth) {
// 使用first_intersection而非all_intersections
// 对于阴影射线,使用do_intersect提前退出
auto intersection = tree.first_intersection(ray);
if(\!intersection) return background_color;
// 处理着色...
// 反射/折射射线
if(depth > 0) {
Ray reflected_ray = compute_reflection(ray, intersection);
Color reflected = trace_ray(reflected_ray, depth - 1);
// 混合颜色...
}
}
private:
std::vector<Intersection> intersection_buffer;
};16.1.7 关键文件位置
| 文件 | 说明 |
|---|---|
/home/chunibyo/workspace/osc/cgal/AABB_tree/include/CGAL/AABB_tree.h | AABB 树主类 |
/home/chunibyo/workspace/osc/cgal/AABB_tree/include/CGAL/AABB_traits_3.h | 3D Traits 类 |
/home/chunibyo/workspace/osc/cgal/AABB_tree/include/CGAL/AABB_traits_2.h | 2D Traits 类 |
/home/chunibyo/workspace/osc/cgal/AABB_tree/include/CGAL/AABB_primitive.h | 通用图元类 |
/home/chunibyo/workspace/osc/cgal/AABB_tree/include/CGAL/AABB_face_graph_triangle_primitive.h | 面图三角形图元 |
/home/chunibyo/workspace/osc/cgal/AABB_tree/include/CGAL/AABB_halfedge_graph_segment_primitive.h | 半边图线段图元 |
/home/chunibyo/workspace/osc/cgal/AABB_tree/include/CGAL/AABB_tree/internal/AABB_node.h | 树节点实现 |
/home/chunibyo/workspace/osc/cgal/AABB_tree/include/CGAL/AABB_tree/internal/AABB_traversal_traits.h | 遍历 traits |