Files

226 lines
6.8 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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