Files
ViewDesignEngine/include/vde/spatial/bvh.h
T

226 lines
6.8 KiB
C++
Raw Normal View History

/**
* @file bvh.h
* @brief 包围盒层次结构 (Bounding Volume Hierarchy)
*
* 基于二叉树的自适应空间划分结构,适用于三角形网格的快速
* 射线求交、范围查询与最近邻查询。
*
* 默认使用 SAH (Surface Area Heuristic) 启发式策略进行
* 自顶向下构建,在质量和构建时间之间取得较好平衡。
*
* @ingroup spatial
*/
#pragma once
#include <optional>
#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<Triangle3D> {
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<Triangle3D>& 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<Triangle3D> query_range(const AABB3D& range) const override;
/**
* @brief K 近邻查询
*
* 使用最小堆遍历 BVH,按包围盒距离剪枝。
*
* @param point 查询点
* @param k 返回数量
* @return 最近的 k 个三角形
*/
std::vector<Triangle3D> query_knn(const Point3D& point, size_t k) const override;
/**
* @brief 射线查询:返回所有命中的三角形
*
* 深度优先遍历,收集所有与射线相交的叶节点三角形。
* 复杂度 O(log n + k)。
*
* @param ray 查询射线
* @return 按命中顺序排列的三角形列表
*/
std::vector<Triangle3D> 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<HitResult> 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<Node> nodes_; /**< 节点数组(根节点为 nodes_[0] */
std::vector<Triangle3D> 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