/** * @file bvh.h * @brief 包围盒层次结构 (Bounding Volume Hierarchy) * * 基于二叉树的自适应空间划分结构,适用于三角形网格的快速 * 射线求交、范围查询与最近邻查询。 * * 默认使用 SAH (Surface Area Heuristic) 启发式策略进行 * 自顶向下构建,在质量和构建时间之间取得较好平衡。 * * @ingroup spatial */ #pragma once #include #include "vde/spatial/spatial_index.h" #include "vde/core/aabb.h" #include "vde/core/triangle.h" namespace vde::spatial { using core::Point3D; using core::Vector3D; using core::Ray3Dd; using core::Triangle3D; /** * @brief BVH 构建分裂策略 */ enum class BVHSplitStrategy { Middle, /**< 按最长轴中点分裂 */ Equal, /**< 按数量均等分裂 */ SAH /**< Surface Area Heuristic — 最小化射线击中概率期望 */ }; /** * @brief BVH 构建选项 * * 控制 BVH 树的构建行为和质量参数。 */ struct BVHBuildOptions { BVHSplitStrategy strategy = BVHSplitStrategy::SAH; /**< 分裂策略 */ int leaf_size = 4; /**< 叶节点最大图元数 */ int max_depth = 64; /**< 树的最大深度 */ }; /** * @brief 包围盒层次结构 — 三角形网格加速结构 * * BVH 将三角形集合递归划分为嵌套的包围盒树。 * 每个内部节点存储其子节点包围盒的并集, * 叶节点包含不超过 leaf_size 个三角形的引用。 * * 构建时间复杂度 O(n log n),空间 O(n)。 * 单条射线查询 O(log n),最近命中 O(log n)。 * * 三种分裂策略: * - Middle: 沿最长轴按包围盒中点一分为二,构建最快但质量一般 * - Equal: 按三角形数量平分,适合均匀分布 * - SAH: 最小化随机射线命中成本期望,构建慢但查询最快(默认) * * @code{.cpp} * BVH bvh(BVHBuildOptions{ .strategy = BVHSplitStrategy::SAH, .leaf_size = 8 }); * bvh.build(triangles); * auto hit = bvh.query_ray_nearest(ray); * auto near = bvh.query_knn(p, 10); * @endcode * * @see SpatialIndex, Octree, KDTree, RTree */ class BVH : public SpatialIndex { public: /** * @brief 构造 BVH 并指定构建选项 * * @param opts 构建选项(分裂策略、叶节点大小、最大深度) */ explicit BVH(const BVHBuildOptions& opts = {}) : opts_(opts) {} /** * @brief 清除已有数据并重建 BVH * * 使用 SAH(或指定策略)自顶向下递归构建。 * 构建复杂度 O(n log n)。 * * @param tris 三角形数组 */ void build(const std::vector& tris) override; /** * @brief 插入单个三角形 * * 当前实现:加入图元列表后全量重建。 * 如需增量插入,建议使用 RTree。 * * @param tri 要插入的三角形 */ void insert(const Triangle3D& tri) override; /** * @brief 删除三角形 * * @param tri 要删除的三角形 * @return 删除成功返回 true */ bool remove(const Triangle3D& tri) override; /** * @brief AABB 范围查询 * * 遍历与 range 相交的 BVH 节点,收集叶节点中重叠的三角形。 * 复杂度 O(log n + k),k 为结果数量。 * * @param range 查询包围盒 * @return 与 range 相交的三角形列表 */ std::vector query_range(const AABB3D& range) const override; /** * @brief K 近邻查询 * * 使用最小堆遍历 BVH,按包围盒距离剪枝。 * * @param point 查询点 * @param k 返回数量 * @return 最近的 k 个三角形 */ std::vector query_knn(const Point3D& point, size_t k) const override; /** * @brief 射线查询:返回所有命中的三角形 * * 深度优先遍历,收集所有与射线相交的叶节点三角形。 * 复杂度 O(log n + k)。 * * @param ray 查询射线 * @return 按命中顺序排列的三角形列表 */ std::vector query_ray(const Ray3Dd& ray) const override; /** * @brief 清空 BVH 树和所有三角形 */ void clear() override; /** * @brief 三角形数量 * * @return 当前索引的三角形总数 */ size_t size() const override { return primitives_.size(); } /** * @brief 射线最近命中查询结果 */ struct HitResult { double t; /**< 沿射线的参数 t */ Triangle3D tri; /**< 命中的三角形 */ Point3D point; /**< 命中点的世界坐标 = origin + t * dir */ }; /** * @brief 射线最近命中查询 * * 遍历 BVH,维护当前最近 t 值进行剪枝, * 返回射线方向上第一个命中的三角形。 * 典型复杂度 O(log n)。 * * @param ray 查询射线 * @return 最近命中结果(若无命中则返回空) * * @note 比 query_ray() 更高效,仅返回最近命中 * * @code{.cpp} * if (auto hit = bvh.query_ray_nearest(ray)) { * std::cout << "Hit at t=" << hit->t << ", point=" << hit->point << "\n"; * } * @endcode */ std::optional query_ray_nearest(const Ray3Dd& ray) const; private: /** * @brief BVH 节点 * * 内部节点存储左右子节点索引(left, right); * 叶节点存储图元列表的起始索引和数量(first_prim, prim_count)。 */ struct Node { AABB3D bounds; /**< 节点的包围盒(所有子节点/图元的并集) */ int left = -1; /**< 左子节点索引,叶节点为 -1 */ int right = -1; /**< 右子节点索引,叶节点为 -1 */ int first_prim = 0; /**< 叶节点第一个图元的索引 */ int prim_count = 0; /**< 叶节点图元数量 */ /** @brief 判断是否为叶节点 */ bool leaf() const { return left < 0; } }; std::vector nodes_; /**< 节点数组(根节点为 nodes_[0]) */ std::vector primitives_; /**< 图元数组 */ BVHBuildOptions opts_; /**< 构建选项 */ /** * @brief 递归构建 BVH 子树 * * @param node 当前节点索引 * @param first 图元范围的起始索引 * @param count 图元数量 * @param depth 当前递归深度 */ void build_recursive(int node, int first, int count, int depth); /** * @brief 计算 SAH 分裂代价 * * @param n_left 左子树图元数 * @param n_right 右子树图元数 * @param left 左子树包围盒 * @param right 右子树包围盒 * @return SAH 代价估值 */ double sah_cost(int n_left, int n_right, const AABB3D& left, const AABB3D& right) const; }; } // namespace vde::spatial