16.1 AABB 树(Axis-Aligned Bounding Box Tree)

相关章节

16.1.0 类比解释:图书馆的管理系统

生活场景类比

想象你管理一个巨大的图书馆(3D场景),里面有数万本书(几何图元)。当一位读者(查询)问:“我想找关于’古代历史’的书(与射线相交的图元)“,你不可能一本一本地检查每本书。

AABB树就像图书馆的分类系统

  • 根节点:所有书的集合
  • 内部节点:大类(如”历史类”、“科学类”)
  • 叶节点:具体的书(三角形、线段等图元)

查询过程

  1. 读者询问”历史类” → 你先看大类标签(根节点包围盒)
  2. 发现”古代史”子类可能相关 → 进入该子树
  3. 发现”现代史”不相关 → 跳过整个子树(剪枝)
  4. 在”古代史”子类中逐本检查(测试叶节点图元)

关键洞察:通过层次化的分类,我们避免了检查不相关的书,将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 树支持以下主要查询类型:

  1. 相交检测(Intersection Detection)

    • 判断查询对象(射线、线段、包围盒等)是否与树中任何图元相交
    • 返回所有相交的图元或第一个相交的图元
  2. 距离查询(Distance Queries)

    • 计算查询点到树中图元的最近距离
    • 查找最近点及其所在的图元
  3. 射线投射(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 是相交图元数量。

性能优化建议

  1. 批量构建:尽可能一次性构建树,而非逐个插入图元
  2. 使用内部 KD 树:调用 accelerate_distance_queries() 可显著提升距离查询性能
  3. 自定义分割策略:对于特定分布的数据,可实现自定义的 Split_primitives
  4. 缓存图元数据:使用 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.hAABB 树主类
/home/chunibyo/workspace/osc/cgal/AABB_tree/include/CGAL/AABB_traits_3.h3D Traits 类
/home/chunibyo/workspace/osc/cgal/AABB_tree/include/CGAL/AABB_traits_2.h2D 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