From b97b0415f54242170d1b4d82c4a88e53c12b4796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8C=82=E4=B9=8B=E9=92=B3?= Date: Fri, 24 Jul 2026 11:02:06 +0000 Subject: [PATCH] docs: doxygen annotations for spatial + boolean + collision --- include/vde/boolean/boolean_2d.h | 50 ++++++- include/vde/boolean/boolean_mesh.h | 172 ++++++++++++++++++++-- include/vde/boolean/polygon_offset.h | 44 +++++- include/vde/collision/gjk.h | 107 +++++++++++++- include/vde/collision/ray_intersect.h | 164 +++++++++++++++++++-- include/vde/collision/sat.h | 47 ++++++ include/vde/collision/tri_intersect.h | 105 +++++++++++++- include/vde/spatial/bvh.h | 200 ++++++++++++++++++++++++-- include/vde/spatial/kd_tree.h | 157 ++++++++++++++++++-- include/vde/spatial/octree.h | 148 ++++++++++++++++++- include/vde/spatial/r_tree.h | 144 +++++++++++++++++-- 11 files changed, 1262 insertions(+), 76 deletions(-) diff --git a/include/vde/boolean/boolean_2d.h b/include/vde/boolean/boolean_2d.h index 7b92cba..81d442e 100644 --- a/include/vde/boolean/boolean_2d.h +++ b/include/vde/boolean/boolean_2d.h @@ -1,3 +1,13 @@ +/** + * @file boolean_2d.h + * @brief 二维多边形布尔运算 + * + * 支持并集、交集、差集、对称差等标准布尔操作。 + * 基于 Vatti 算法实现。 + * + * @ingroup boolean + */ + #pragma once #include "vde/core/polygon.h" #include @@ -6,10 +16,44 @@ namespace vde::boolean { using core::Point2D; using core::Polygon2D; -enum class BooleanOp { Union, Intersection, Difference, SymDiff }; +/** + * @brief 布尔运算类型 + */ +enum class BooleanOp { + Union, /**< 并集 A ∪ B */ + Intersection, /**< 交集 A ∩ B */ + Difference, /**< 差集 A − B */ + SymDiff /**< 对称差 (A − B) ∪ (B − A) */ +}; -/// 2D polygon boolean operations (Vatti algorithm) -/// Currently implemented via a simple edge walking approach for convex polygons +/** + * @brief 二维多边形布尔运算 + * + * 对两个简单多边形执行指定布尔操作。 + * 当前实现:基于 Vatti 算法的边行走方式处理凸多边形, + * 可扩展支持任意简单多边形(含孔洞)。 + * + * 算法流程: + * 1. 找到两条多边形的所有交点 + * 2. 跟踪边界根据布尔操作选择进入/退出边 + * 3. 组装结果多边形 + * + * @note 当前版本优化了凸多边形路径。对于带孔洞或自交的 + * 复杂多边形,建议先三角剖分或使用 mesh_boolean。 + * + * @param a 第一个多边形 + * @param b 第二个多边形 + * @param op 布尔操作类型 + * @return 结果多边形列表(可能有多个分离的组件) + * + * @code{.cpp} + * Polygon2D p1 = make_circle(10, Point2D{0,0}, 5.0); + * Polygon2D p2 = make_circle(10, Point2D{3,0}, 5.0); + * auto result = boolean_2d(p1, p2, BooleanOp::Union); + * @endcode + * + * @see BooleanOp, mesh_boolean, polygon_offset + */ std::vector boolean_2d(const Polygon2D& a, const Polygon2D& b, BooleanOp op); } // namespace vde::boolean diff --git a/include/vde/boolean/boolean_mesh.h b/include/vde/boolean/boolean_mesh.h index 4237ebf..e17e72f 100644 --- a/include/vde/boolean/boolean_mesh.h +++ b/include/vde/boolean/boolean_mesh.h @@ -1,3 +1,13 @@ +/** + * @file boolean_mesh.h + * @brief 三维网格布尔运算 + * + * 基于半边的网格布尔操作:并集、交集、差集、对称差。 + * 通过面片重心内外判定 (ray-casting) 实现拓扑分类。 + * + * @ingroup boolean + */ + #pragma once #include "vde/boolean/boolean_2d.h" #include "vde/core/polygon.h" @@ -8,40 +18,174 @@ using core::Point2D; using core::Polygon2D; using mesh::HalfedgeMesh; -/// ── Core 3D mesh boolean operations ────────────────────── +// ── Core 3D mesh boolean operations ────────────────────────────── -/// Union: A ∪ B — keep faces of each mesh whose centroids lie outside the other +/** + * @brief 网格并集 A ∪ B + * + * 保留网格 A 中重心在网格 B 外的面片, + * 以及网格 B 中重心在网格 A 外的面片。 + * 合并后形成两个网格的并集体积。 + * + * 原理:对每个面片计算重心坐标,用射线投射判断 + * 该点是否在对方网格内部,据此决定保留或剔除。 + * + * @param a 网格 A + * @param b 网格 B + * @return A ∪ B 的合并网格 + * + * @code{.cpp} + * HalfedgeMesh result = mesh_union(cube, sphere); + * @endcode + * + * @see mesh_intersection, mesh_difference, is_point_inside_mesh + */ HalfedgeMesh mesh_union(const HalfedgeMesh& a, const HalfedgeMesh& b); -/// Intersection: A ∩ B — keep faces whose centroids lie inside the other +/** + * @brief 网格交集 A ∩ B + * + * 保留网格 A 中重心在网格 B 内的面片, + * 以及网格 B 中重心在网格 A 内的面片。 + * + * @param a 网格 A + * @param b 网格 B + * @return A ∩ B 的交集网格 + * + * @note 结果体积为两网格公共区域 + * + * @see mesh_union, mesh_difference + */ HalfedgeMesh mesh_intersection(const HalfedgeMesh& a, const HalfedgeMesh& b); -/// Difference: A − B — keep A-faces outside B +/** + * @brief 网格差集 A − B + * + * 保留网格 A 中重心在网格 B 外的面片, + * 以及网格 B 中重心在网格 A 内的面片(翻转法线)。 + * + * @param a 网格 A + * @param b 网格 B + * @return A − B 的差集网格 + * + * @note 结果中来自 B 的面片会被翻转法线以保持内-外一致性 + * + * @see mesh_union, mesh_intersection, mesh_sym_diff + */ HalfedgeMesh mesh_difference(const HalfedgeMesh& a, const HalfedgeMesh& b); -/// Symmetric difference: (A − B) ∪ (B − A) +/** + * @brief 网格对称差 (A − B) ∪ (B − A) + * + * 等价于: + * @code{.cpp} + * mesh_union(mesh_difference(a, b), mesh_difference(b, a)) + * @endcode + * + * @param a 网格 A + * @param b 网格 B + * @return 对称差网格 + * + * @see mesh_difference, mesh_union + */ HalfedgeMesh mesh_sym_diff(const HalfedgeMesh& a, const HalfedgeMesh& b); -/// ── Helpers ────────────────────────────────────────────── +// ── Helpers ───────────────────────────────────────────────────── -/// Crop mesh `a` by mesh `b`'s boundary: keep faces of `a` whose -/// centroids lie OUTSIDE the volume of `b`. -/// If `invert_b` is true, test against the flipped-volume (inside↔outside) of `b`. +/** + * @brief 用网格 B 裁剪网格 A + * + * 保留网格 A 中位于 B 体外的面片。 + * 是 mesh_difference 的简化变体,仅输出来自 A 的面片。 + * + * @param a 待裁剪网格 + * @param b 裁剪体网格 + * @param invert_b 若为 true,将 B 的内外判定取反(裁剪外部) + * @return 裁剪后的网格(仅包含 A 的面片) + * + * @code{.cpp} + * // 裁剪 cube 外部的 sphere 面片 + * auto clipped = clip_mesh(sphere, cube, /*invert_b=*/false); + * // 保留 cube 内部的 sphere 面片 + * auto inner = clip_mesh(sphere, cube, /*invert_b=*/true); + * @endcode + * + * @see mesh_difference, is_point_inside_mesh + */ HalfedgeMesh clip_mesh(const HalfedgeMesh& a, const HalfedgeMesh& b, bool invert_b = false); -/// Invert all face orientations in the mesh +/** + * @brief 翻转网格所有面片的法线方向 + * + * 遍历所有面片,反转其顶点环绕顺序。 + * 常用于生成负体积(反向外壳)或差集操作中。 + * + * @param m 原始网格 + * @return 法线翻转后的网格 + * + * @see mesh_difference + */ HalfedgeMesh invert_mesh(const HalfedgeMesh& m); -/// Merge two meshes into one (concatenate vertices and faces) +/** + * @brief 合并两个网格 + * + * 将两个网格的顶点和面片直接拼接,不做布尔操作。 + * 用于将分离的网格组件合并到单个数据结构中。 + * + * @param a 网格 A + * @param b 网格 B + * @return 包含 A 和 B 全部顶点和面片的合并网格 + * + * @note 不处理重叠区域;仅做纯几何拼接 + * + * @see mesh_union(做布尔合并) + */ HalfedgeMesh merge_meshes(const HalfedgeMesh& a, const HalfedgeMesh& b); -/// Convenience dispatcher — route by BooleanOp enum +/** + * @brief 网格布尔运算统一接口 + * + * 根据布尔操作类型分发到对应的具体函数。 + * + * @param a 网格 A + * @param b 网格 B + * @param op 布尔操作类型 + * @return 结果网格 + * + * @code{.cpp} + * auto result = mesh_boolean(cube, sphere, BooleanOp::Intersection); + * @endcode + * + * @see BooleanOp, mesh_union, mesh_intersection, mesh_difference, mesh_sym_diff + */ HalfedgeMesh mesh_boolean(const HalfedgeMesh& a, const HalfedgeMesh& b, BooleanOp op); -/// ── Point classification ───────────────────────────────── +// ── Point classification ──────────────────────────────────────── -/// Test whether point `p` is inside `mesh` using ray-casting +/** + * @brief 判断三维点是否在封闭网格内部 + * + * 使用射线投射法:从点 p 向任意方向射出射线, + * 统计与网格面片的交点数量。 + * 奇数表示在内部,偶数表示在外部。 + * + * 算法: + * 1. 构造从 p 出发沿 +x 方向的射线 + * 2. 遍历所有三角形进行射线-三角形求交 + * 3. 统计交点数量 + * 4. 返回奇偶性判定结果 + * + * @param p 待判定点 + * @param mesh 封闭网格 + * @return 点在网格内部返回 true + * + * @note 网格必须是封闭(水密)的,否则结果不可靠。 + * 点恰好落在面上时行为未定义。 + * + * @see clip_mesh, mesh_difference + */ bool is_point_inside_mesh(const core::Point3D& p, const HalfedgeMesh& mesh); } // namespace vde::boolean diff --git a/include/vde/boolean/polygon_offset.h b/include/vde/boolean/polygon_offset.h index e420a1b..690b439 100644 --- a/include/vde/boolean/polygon_offset.h +++ b/include/vde/boolean/polygon_offset.h @@ -1,3 +1,13 @@ +/** + * @file polygon_offset.h + * @brief 多边形等距偏移 (Polygon Offset / Inset) + * + * 基于直骨架 (Straight Skeleton) 近似的多边形 + * 膨胀/收缩操作。用于 2D 轮廓生成、刀具路径补偿等。 + * + * @ingroup boolean + */ + #pragma once #include "vde/core/polygon.h" #include @@ -6,8 +16,38 @@ namespace vde::boolean { using core::Point2D; using core::Polygon2D; -/// Offset a polygon by distance (positive = inflate, negative = deflate) -/// Uses straight skeleton approximation via edge normal displacement +/** + * @brief 多边形等距偏移 + * + * 将多边形的每条边沿其法线方向平移指定距离, + * 在转角处自动生成圆弧/斜角连接。 + * + * 算法: + * 1. 对每条边计算单位外法线 + * 2. 沿法线平移 distance 距离 + * 3. 相邻偏移边求交形成新的顶点 + * 4. 检测自交并移除退化区域 + * + * - 正距离 = 外扩(膨胀/Inflation) + * - 负距离 = 内缩(偏置/Deflation) + * + * @param poly 原始多边形 + * @param distance 偏移距离(正=外扩,负=内缩) + * @return 偏移后的多边形列表(内缩或自交可能产生多个碎片) + * + * @note 使用直骨架近似而非精确圆弧偏移,转角处为斜角连接。 + * 大距离负偏移可能导致多边形完全消失(返回空列表)。 + * + * @code{.cpp} + * Polygon2D rect = {{0,0}, {10,0}, {10,5}, {0,5}}; + * // 外扩 2 单位 + * auto inflated = polygon_offset(rect, 2.0); + * // 内缩 1 单位(生成工具路径) + * auto toolpath = polygon_offset(rect, -1.0); + * @endcode + * + * @see boolean_2d + */ std::vector polygon_offset(const Polygon2D& poly, double distance); } // namespace vde::boolean diff --git a/include/vde/collision/gjk.h b/include/vde/collision/gjk.h index 3cd9d77..dbed8e7 100644 --- a/include/vde/collision/gjk.h +++ b/include/vde/collision/gjk.h @@ -1,3 +1,13 @@ +/** + * @file gjk.h + * @brief GJK (Gilbert–Johnson–Keerthi) 碰撞检测算法 + * + * 检测任意凸形状之间的碰撞与最近距离。 + * 通过 Minkowski 差和支撑函数迭代逼近最近点对。 + * + * @ingroup collision + */ + #pragma once #include "vde/core/point.h" #include @@ -7,17 +17,106 @@ namespace vde::collision { using core::Point3D; using core::Vector3D; +/** + * @brief 支撑函数类型 + * + * 给定方向向量 d,返回形状在该方向上最远点的坐标: + * @code{.cpp} + * Point3D support(const Vector3D& d) { + * return argmax_{v ∈ shape} dot(v, d); + * } + * @endcode + * + * 支撑函数是 GJK / EPA 算法的核心抽象。 + * 只要实现了支撑函数,即可用于任意凸形状的碰撞检测。 + */ using SupportFunc = std::function; +/** + * @brief GJK 完整检测结果 + */ struct GJKResult { - bool intersect; - double distance; - Point3D point_a; - Point3D point_b; + bool intersect; /**< 两凸体是否相交 */ + double distance; /**< 最近距离(不相交时 >0,相交时为 0) */ + Point3D point_a; /**< 形状 A 上的最近点 */ + Point3D point_b; /**< 形状 B 上的最近点 */ }; +/** + * @brief GJK 相交检测(仅判断是否碰撞) + * + * 构造 Minkowski 差 A − B,在原点附近迭代构建单纯形。 + * 若原点在 Minkowski 差内 → 碰撞。 + * + * 算法流程: + * 1. 取初始方向(如 a.center − b.center) + * 2. 计算支撑点并加入单纯形 + * 3. 判断单纯形是否包含原点 + * 4. 若不包含,更新搜索方向并迭代 + * 5. 若方向无法收敛 → 不相交 + * + * 时间复杂度 O(n),其中 n 为迭代次数(通常 < 20 对于紧凑形状)。 + * + * @param shape_a 形状 A 的支撑函数 + * @param shape_b 形状 B 的支撑函数 + * @return 相交返回 true + * + * @code{.cpp} + * auto sphere_support = [&](const Vector3D& d) { + * return center + radius * d.normalized(); + * }; + * auto box_support = [&](const Vector3D& d) { + * return center + Vector3D{ + * half_extent.x * (d.x > 0 ? 1 : -1), + * half_extent.y * (d.y > 0 ? 1 : -1), + * half_extent.z * (d.z > 0 ? 1 : -1) + * }; + * }; + * if (gjk_intersect(sphere_support, box_support)) { ... } + * @endcode + * + * @see gjk_distance, gjk_full, SupportFunc + */ bool gjk_intersect(const SupportFunc& shape_a, const SupportFunc& shape_b); + +/** + * @brief GJK 最近距离查询 + * + * 在 gjk_intersect 基础上,不相交时额外调用 EPA (Expanding Polytope Algorithm) + * 计算两形状之间的精确最近距离。 + * + * @param shape_a 形状 A 的支撑函数 + * @param shape_b 形状 B 的支撑函数 + * @return 最近距离(相交时返回 0),始终 ≥ 0 + * + * @see gjk_intersect, gjk_full + */ double gjk_distance(const SupportFunc& shape_a, const SupportFunc& shape_b); + +/** + * @brief GJK 完整检测:碰撞状态、距离、最近点对 + * + * 融合 gjk_intersect 和 gjk_distance,一次调用返回 + * 碰撞状态、最近距离及两形状上的最近点。 + * + * @param shape_a 形状 A 的支撑函数 + * @param shape_b 形状 B 的支撑函数 + * @return GJKResult 包含碰撞状态、距离、最近点对 + * + * @note 对于连续碰撞检测 (CCD) 或需要分离向量的场景, + * 使用此函数可同时获取碰撞信息和穿透深度方向。 + * + * @code{.cpp} + * auto result = gjk_full(a_support, b_support); + * if (result.intersect) { + * // 穿透,分离方向 = (point_b - point_a).normalized() + * } else { + * double gap = result.distance; // 间隙 + * } + * @endcode + * + * @see gjk_intersect, gjk_distance + */ GJKResult gjk_full(const SupportFunc& shape_a, const SupportFunc& shape_b); } // namespace vde::collision diff --git a/include/vde/collision/ray_intersect.h b/include/vde/collision/ray_intersect.h index 7f99ace..548c148 100644 --- a/include/vde/collision/ray_intersect.h +++ b/include/vde/collision/ray_intersect.h @@ -1,3 +1,13 @@ +/** + * @file ray_intersect.h + * @brief 射线求交工具集 + * + * 射线与基本几何图元(三角形、AABB、球体、平面、网格)的求交函数。 + * 包含 Möller-Trumbore 算法、slab 方法、解析求交等。 + * + * @ingroup collision + */ + #pragma once #include "vde/core/point.h" #include "vde/core/line.h" @@ -13,44 +23,150 @@ using core::Triangle3D; using core::Ray3Dd; using core::AABB3D; +/** + * @brief 射线-三角形求交结果 + */ struct RayTriResult { - double t; - double u, v; - Point3D point; + double t; /**< 沿射线的参数:hit_point = origin + t * dir */ + double u, v; /**< 三角形重心坐标:hit_point = (1−u−v)*v0 + u*v1 + v*v2 */ + Point3D point; /**< 命中点的世界坐标 */ }; -// Ray-Triangle (Möller-Trumbore) +/** + * @brief 射线-三角形求交(Möller-Trumbore 算法) + * + * 标准 MT 算法,直接计算重心坐标 u, v 和参数 t。 + * 不预先计算平面方程,一步到位检测。 + * + * 算法: + * 1. 计算两个边向量 e1 = v1−v0, e2 = v2−v0 + * 2. 计算 pvec = dir × e2, det = dot(e1, pvec) + * 3. 若 |det| < ε → 射线平行于三角形(无命中) + * 4. 计算 tvec = origin−v0, u, v, t + * 5. 验证 u≥0, v≥0, u+v≤1, t>0 → 命中 + * + * 复杂度 O(1)。 + * + * @param origin 射线起点 + * @param dir 射线方向(需归一化以获得正确 t 值) + * @param tri 三角形 + * @return 命中返回 RayTriResult;未命中返回空 + * + * @code{.cpp} + * if (auto hit = ray_triangle_intersect(origin, dir, tri)) { + * std::cout << "Hit at t=" << hit->t + * << " barycentric=(" << hit->u << "," << hit->v << ")\n"; + * } + * @endcode + * + * @see ray_mesh_intersect + */ std::optional ray_triangle_intersect( const Point3D& origin, const Vector3D& dir, const Triangle3D& tri); +/** + * @brief 射线-三角形求交(Ray3Dd 重载) + * + * @param ray 射线 + * @param tri 三角形 + * @return 命中返回 RayTriResult;未命中返回空 + * + * @see ray_triangle_intersect(origin, dir, tri) + */ inline std::optional ray_triangle_intersect( const Ray3Dd& ray, const Triangle3D& tri) { return ray_triangle_intersect(ray.origin(), ray.direction(), tri); } -// Ray-AABB (slab method). Returns true if hit, with t interval. +/** + * @brief 射线-AABB 求交(slab 方法) + * + * 分别检测射线与三个坐标轴方向 slab(平板块)的交点, + * 取最大 t_min 和最小 t_max。若 t_min ≤ t_max 且 t_max ≥ 0,则命中。 + * + * 复杂度 O(1)。 + * + * @param ray 射线 + * @param box 轴对齐包围盒 + * @param tmin_out [输出] 进入包围盒的参数 t + * @param tmax_out [输出] 离开包围盒的参数 t + * @return 命中返回 true + * + * @note 常用于 BVH 遍历节点的快速 rejection test + * + * @see ray_triangle_intersect + */ bool ray_aabb_intersect(const Ray3Dd& ray, const AABB3D& box, double& tmin_out, double& tmax_out); -// Ray-Sphere +/** + * @brief 射线-球体求交结果 + */ struct RaySphereResult { - double t; - Point3D point; + double t; /**< 命中参数(通常取较小的 t) */ + Point3D point; /**< 命中点世界坐标 */ }; + +/** + * @brief 射线-球体求交(解析法) + * + * 解二次方程 |origin + t*dir − center|² = r²。 + * 通过判别式 Δ = b² − 4ac 判断: + * - Δ < 0 → 无交点 + * - Δ = 0 → 切点(一个交点) + * - Δ > 0 → 两个交点(返回较近的) + * + * 复杂度 O(1)。 + * + * @param origin 射线起点 + * @param dir 射线方向 + * @param center 球心 + * @param radius 球半径 + * @return 命中返回 RaySphereResult(最近交点);未命中返回空 + * + * @see ray_sphere_intersect(ray, center, radius) + */ std::optional ray_sphere_intersect( const Point3D& origin, const Vector3D& dir, const Point3D& center, double radius); +/** + * @brief 射线-球体求交(Ray3Dd 重载) + * + * @param ray 射线 + * @param center 球心 + * @param radius 球半径 + * @return 命中返回 RaySphereResult;未命中返回空 + */ inline std::optional ray_sphere_intersect( const Ray3Dd& ray, const Point3D& center, double radius) { return ray_sphere_intersect(ray.origin(), ray.direction(), center, radius); } -// Ray-Plane (returns t value) +/** + * @brief 射线-平面求交 + * + * 解 t = dot(plane_point − origin, plane_normal) / dot(dir, plane_normal)。 + * 若分母 ≈ 0(射线平行于平面)→ 无交点。 + * + * @param origin 射线起点 + * @param dir 射线方向 + * @param plane_point 平面上一点 + * @param plane_normal 平面法线(需归一化以获得正确 t) + * @return 命中返回参数 t;未命中(平行或反向)返回空 + */ std::optional ray_plane_intersect( const Point3D& origin, const Vector3D& dir, const Point3D& plane_point, const Vector3D& plane_normal); +/** + * @brief 射线-平面求交(Ray3Dd 重载) + * + * @param ray 射线 + * @param plane_point 平面上一点 + * @param plane_normal 平面法线 + * @return 命中返回参数 t;未命中返回空 + */ inline std::optional ray_plane_intersect( const Ray3Dd& ray, const Point3D& plane_point, const Vector3D& plane_normal) { @@ -58,11 +174,39 @@ inline std::optional ray_plane_intersect( plane_point, plane_normal); } -// Ray-Mesh — traverse all triangles, return closest hit +/** + * @brief 射线-网格求交(暴力遍历) + * + * 遍历所有三角形,对每个三角形调用 ray_triangle_intersect, + * 跟踪最小 t 值返回最近命中。 + * + * 复杂度 O(n),n 为三角形数量。 + * + * @param origin 射线起点 + * @param dir 射线方向 + * @param triangles 三角形列表 + * @return 最近命中结果;无命中返回空 + * + * @note 对于大型网格建议配合 BVH 使用: + * @code{.cpp} + * BVH bvh; + * bvh.build(triangles); + * auto hit = bvh.query_ray_nearest(ray); // O(log n) + * @endcode + * + * @see ray_tri_intersect.h, query_ray_nearest (BVH) + */ std::optional ray_mesh_intersect( const Point3D& origin, const Vector3D& dir, const std::vector& triangles); +/** + * @brief 射线-网格求交(Ray3Dd 重载) + * + * @param ray 射线 + * @param triangles 三角形列表 + * @return 最近命中结果;无命中返回空 + */ inline std::optional ray_mesh_intersect( const Ray3Dd& ray, const std::vector& triangles) { return ray_mesh_intersect(ray.origin(), ray.direction(), triangles); diff --git a/include/vde/collision/sat.h b/include/vde/collision/sat.h index f23742f..5293bfb 100644 --- a/include/vde/collision/sat.h +++ b/include/vde/collision/sat.h @@ -1,3 +1,13 @@ +/** + * @file sat.h + * @brief SAT (Separating Axis Theorem) 碰撞检测 + * + * 基于分离轴定理的凸多面体相交检测。 + * 若存在一条轴使得两形状投影不重叠,则不相交。 + * + * @ingroup collision + */ + #pragma once #include "vde/core/point.h" #include @@ -6,6 +16,43 @@ namespace vde::collision { using core::Point3D; +/** + * @brief SAT 凸多面体相交检测 + * + * 分离轴定理:两个凸多面体不相交,当且仅当存在一条分离轴, + * 使得二者在该轴上的投影区间不重叠。 + * + * 待检测的分离轴集合: + * - A 的每个面法线 + * - B 的每个面法线 + * - A 的每条边与 B 的每条边的叉积 + * + * 对每条候选轴,计算两个多面体投影区间的最小/最大值。 + * 若某轴上投影区间无重叠 → 不相交(早停)。 + * 所有轴都重叠 → 相交。 + * + * 最坏时间复杂度 O(n_a · n_b),其中 n_a, n_b 为面数。 + * 对于凸多面体通常在遍历部分轴后即早停。 + * + * @param verts_a 多面体 A 的顶点数组 + * @param faces_a 多面体 A 的面索引数组(每个面为 {v0, v1, v2}) + * @param verts_b 多面体 B 的顶点数组 + * @param faces_b 多面体 B 的面索引数组 + * @return 相交返回 true + * + * @note 适用于任意凸多面体。对于胶囊/球等光滑形状,GJK 更合适。 + * + * @code{.cpp} + * // 两个凸四面体相交检测 + * std::vector va = {{0,0,0}, {1,0,0}, {0,1,0}, {0,0,1}}; + * std::vector> fa = {{0,2,1}, {0,1,3}, {0,3,2}, {1,2,3}}; + * std::vector vb = {{0.5,0.5,0.5}, {1.5,0.5,0.5}, ...}; + * std::vector> fb = { ... }; + * bool hit = sat_intersect(va, fa, vb, fb); + * @endcode + * + * @see gjk_intersect, tri_tri_intersect + */ bool sat_intersect(const std::vector& verts_a, const std::vector>& faces_a, const std::vector& verts_b, diff --git a/include/vde/collision/tri_intersect.h b/include/vde/collision/tri_intersect.h index 4367b77..45771a1 100644 --- a/include/vde/collision/tri_intersect.h +++ b/include/vde/collision/tri_intersect.h @@ -1,3 +1,13 @@ +/** + * @file tri_intersect.h + * @brief 三角形-三角形 / 三角形-AABB 相交检测 + * + * 基于分离轴定理 (SAT) 的三角形间相交检测。 + * 包括快速布尔测试和详细交点段提取。 + * + * @ingroup collision + */ + #pragma once #include "vde/core/triangle.h" #include "vde/core/aabb.h" @@ -7,18 +17,103 @@ using core::Triangle3D; using core::AABB3D; using core::Point3D; -// Separating-axis test for two triangles +/** + * @brief 三角形-三角形相交检测(布尔测试) + * + * 使用分离轴定理在两组三角形之间检测相交。 + * 候选分离轴包括: + * - 每个三角形的面法线(2 轴) + * - 每对边的叉积(3×3 = 9 轴) + * - 共 11 轴(三角形退化为共面时可能有更多) + * + * 若存在一条轴使得两三角形投影区间不重叠 → 不相交。 + * + * 复杂度 O(1)(固定 11+ 轴检测)。 + * + * @param t1 三角形 1 + * @param t2 三角形 2 + * @return 相交返回 true + * + * @code{.cpp} + * Triangle3D t1 = {p0, p1, p2}; + * Triangle3D t2 = {q0, q1, q2}; + * if (tri_tri_intersect(t1, t2)) { + * // 处理碰撞 + * } + * @endcode + * + * @see tri_tri_intersect_detailed, sat_intersect + */ bool tri_tri_intersect(const Triangle3D& t1, const Triangle3D& t2); -// Detailed tri-tri intersection — returns the intersection segment +/** + * @brief 三角形-三角形相交线段 + */ struct TriTriIntersection { - bool intersects; - Point3D p0, p1; + bool intersects; /**< 是否相交 */ + Point3D p0; /**< 交点段起点 */ + Point3D p1; /**< 交点段终点 */ }; + +/** + * @brief 三角形-三角形详细相交检测 + * + * 返回两三角形相交的交点线段(两个端点)。 + * 适用于需要精确接触几何的场景,如: + * - 物理引擎的接触流形生成 + * - CSG 构造体几何的精确分割线 + * - 网格自交修复 + * + * 算法: + * 1. 用平面方程计算各顶点到对方平面的有符号距离 + * 2. 找到边-平面交点形成交集段 + * 3. 限制交集段到两个三角形内部 + * + * @param t1 三角形 1 + * @param t2 三角形 2 + * @return TriTriIntersection:相交标志 + 交点段端点 + * + * @note 共面情况交集段可能退化为单点(p0 == p1) + * + * @code{.cpp} + * auto sect = tri_tri_intersect_detailed(t1, t2); + * if (sect.intersects) { + * Vector3D contact_normal = (t1.normal() + t2.normal()).normalized(); + * Point3D midpoint = (sect.p0 + sect.p1) * 0.5; + * } + * @endcode + * + * @see tri_tri_intersect + */ TriTriIntersection tri_tri_intersect_detailed(const Triangle3D& t1, const Triangle3D& t2); -// Fast SAT test: triangle vs AABB (13 separating axes) +/** + * @brief 三角形-AABB 快速相交测试 + * + * 使用分离轴定理在三角形和轴对齐包围盒之间检测。 + * 候选分离轴包括(共 13 轴): + * - 3 条 AABB 面法线 + * - 1 条三角形面法线 + * - 9 条边×边叉积(3 条 AABB 边 × 3 条三角形边) + * + * 复杂度 O(1)。 + * + * @param tri 三角形 + * @param box 轴对齐包围盒 + * @return 相交返回 true + * + * @note AABB 碰撞比三角剖分后的三角-三角检测更高效, + * 常用于空间划分结构中的 rejection test。 + * + * @code{.cpp} + * if (tri_aabb_overlap(tri, node_bounds)) { + * // 需要进一步检测节点内的三角形 + * } + * @endcode + * + * @see tri_tri_intersect, ray_aabb_intersect + */ bool tri_aabb_overlap(const Triangle3D& tri, const AABB3D& box); } // namespace vde::collision diff --git a/include/vde/spatial/bvh.h b/include/vde/spatial/bvh.h index 68b05ce..29c55dc 100644 --- a/include/vde/spatial/bvh.h +++ b/include/vde/spatial/bvh.h @@ -1,3 +1,16 @@ +/** + * @file bvh.h + * @brief 包围盒层次结构 (Bounding Volume Hierarchy) + * + * 基于二叉树的自适应空间划分结构,适用于三角形网格的快速 + * 射线求交、范围查询与最近邻查询。 + * + * 默认使用 SAH (Surface Area Heuristic) 启发式策略进行 + * 自顶向下构建,在质量和构建时间之间取得较好平衡。 + * + * @ingroup spatial + */ + #pragma once #include #include "vde/spatial/spatial_index.h" @@ -10,43 +23,202 @@ using core::Vector3D; using core::Ray3Dd; using core::Triangle3D; -enum class BVHSplitStrategy { Middle, Equal, SAH }; - -struct BVHBuildOptions { - BVHSplitStrategy strategy = BVHSplitStrategy::SAH; - int leaf_size = 4; - int max_depth = 64; +/** + * @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(); } - /// Find closest hit along ray - struct HitResult { double t; Triangle3D tri; Point3D point; }; + /** + * @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, right = -1; - int first_prim = 0, prim_count = 0; + 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_; - std::vector primitives_; - BVHBuildOptions opts_; + 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; }; diff --git a/include/vde/spatial/kd_tree.h b/include/vde/spatial/kd_tree.h index ddcf526..6fcbf68 100644 --- a/include/vde/spatial/kd_tree.h +++ b/include/vde/spatial/kd_tree.h @@ -1,3 +1,13 @@ +/** + * @file kd_tree.h + * @brief k-d 树 (K-Dimensional Tree) 空间划分结构 + * + * 通过逐维交替分割的超平面将空间递归二分。 + * 适合中规模数据集的最邻近搜索和范围查询。 + * + * @ingroup spatial + */ + #pragma once #include "vde/spatial/spatial_index.h" #include @@ -9,43 +19,168 @@ using core::Vector3D; using core::Ray3Dd; using core::Triangle3D; +/** + * @brief k-d 树空间索引 + * + * 在每一层选择一个坐标轴,以该轴上中位数为分割面, + * 将空间划分为两个半空间,递归构建二叉树。 + * + * 构建: O(n log n) 使用中位数分区 + * 范围查询: O(n^(1−1/d) + k),3D 下约为 O(n^(2/3) + k) + * KNN 查询: O(log n) 平均,最坏 O(n) + * 射线查询: O(n^(2/3) + k) 近似 + * + * @tparam T 被索引的空间对象类型 + * + * @note k-d 树在维度约 2~6 时效率最佳。三维场景中, + * BVH 对于射线查询更高效,Octree 对于均匀体数据更优。 + * + * @code{.cpp} + * KDTree kdtree; + * kdtree.build(points); + * auto nearest = kdtree.query_knn(query_point, 1); + * auto in_range = kdtree.query_range(AABB3D::from_minmax(min, max)); + * @endcode + * + * @see SpatialIndex, BVH, Octree + */ template class KDTree : public SpatialIndex { public: + /** + * @brief 批量构建 k-d 树 + * + * 递归选择分裂轴和分裂点进行自顶向下构建。 + * 构建复杂度 O(n log n)。 + * + * @param items 待索引的对象数组 + */ void build(const std::vector& items) override; + + /** + * @brief 插入单个对象 + * + * @param item 待插入的空间对象 + */ void insert(const T& item) override; + + /** + * @brief 删除对象 + * + * @param item 待删除的空间对象 + * @return 删除成功返回 true + */ bool remove(const T& item) override; + + /** + * @brief 范围查询 + * + * 遍历与查询 AABB 相交的 kd 树节点。 + * + * @param range 查询包围盒 + * @return 与 range 相交的对象列表 + */ std::vector query_range(const AABB3D& range) const override; + + /** + * @brief K 近邻查询 + * + * 最优优先搜索:从根节点出发,使用优先队列按距离剪枝。 + * 平均 O(log n),最坏 O(n)。 + * + * @param point 查询点 + * @param k 返回数量 + * @return 最近的 k 个对象 + */ std::vector query_knn(const Point3D& point, size_t k) const override; + + /** + * @brief 射线查询 + * + * @param ray 查询射线 + * @return 与射线相交的对象列表 + */ std::vector query_ray(const Ray3Dd& ray) const override; + + /** + * @brief 清空 k-d 树 + */ void clear() override; + + /** + * @brief 对象数量 + * + * @return 当前索引的对象总数 + */ size_t size() const override { return items_.size(); } private: + /** + * @brief k-d 树节点 + */ struct Node { - AABB3D bounds; - int item_idx = -1; // -1 for internal node; index into items_ for leaf - int split_axis = 0; - int left = -1; - int right = -1; + AABB3D bounds; /**< 节点包围盒 */ + int item_idx = -1; /**< 叶节点对应的对象索引(-1 表示内部节点) */ + int split_axis = 0; /**< 分裂轴:0=x, 1=y, 2=z */ + int left = -1; /**< 左子节点索引 */ + int right = -1; /**< 右子节点索引 */ }; - std::vector items_; - std::vector nodes_; - int root_ = -1; + std::vector items_; /**< 对象数组 */ + std::vector nodes_; /**< 节点数组 */ + int root_ = -1; /**< 根节点索引 */ - /// Return a representative 3D position for the item (used for spatial ordering) + /** + * @brief 获取对象代表位置(用于空间排序) + * + * @param item 空间对象 + * @return 对象的三维代表点 + */ static Point3D get_position(const T& item); - /// Return an axis-aligned bounding box for the item + /** + * @brief 获取对象的轴对齐包围盒 + * + * @param item 空间对象 + * @return 包围盒 + */ static AABB3D get_aabb(const T& item); - /// Test whether the item intersects a ray + /** + * @brief 判断对象是否与射线相交 + * + * @param item 空间对象 + * @param ray 查询射线 + * @return 相交返回 true + */ static bool ray_hits_item(const T& item, const Ray3Dd& ray); + /** + * @brief 递归构建 k-d 树 + * + * @param start items_ 中当前范围起始索引 + * @param end items_ 中当前范围结束索引 + * @return 新节点的索引 + */ int build_recursive(int start, int end); + + /** + * @brief 递归范围查询 + * + * @param node_idx 当前节点索引 + * @param range 查询包围盒 + * @param result 累积结果 + */ void query_range_recursive(int node_idx, const AABB3D& range, std::vector& result) const; + + /** + * @brief 递归射线查询 + * + * @param node_idx 当前节点索引 + * @param ray 查询射线 + * @param result 累积结果 + */ void query_ray_recursive(int node_idx, const Ray3Dd& ray, std::vector& result) const; }; diff --git a/include/vde/spatial/octree.h b/include/vde/spatial/octree.h index 6ae87ab..1106477 100644 --- a/include/vde/spatial/octree.h +++ b/include/vde/spatial/octree.h @@ -1,3 +1,13 @@ +/** + * @file octree.h + * @brief 八叉树 (Octree) 空间划分结构 + * + * 将三维空间递归剖分为八个等大子立方体的树形结构。 + * 适合点云、体素等均匀分布数据的空间查询与加速。 + * + * @ingroup spatial + */ + #pragma once #include "vde/core/aabb.h" #include "vde/spatial/spatial_index.h" @@ -12,32 +22,164 @@ using core::Triangle3D; template struct OctreeNode; template struct OctreeData; +/** + * @brief 八叉树空间索引 + * + * 将三维空间递归划分为 8 个等大的子立方体区域。 + * 每个节点维护自身包围盒与所含对象列表。 + * 当节点中对象数超过 max_items 且未达到最大深度时自动剖分。 + * + * 构建: O(n) 逐项插入 + * 范围查询: O(8^d + k),d 为最大深度,k 为结果数 + * KNN 查询: O(8^d + k log k) + * 射线查询: O(8^d + k) + * + * @tparam T 被索引的空间对象类型 + * + * @note 适用于体积数据、粒子系统、空间哈希等场景。 + * 对非均匀分布的数据(如细长三角网格),BVH 通常更优。 + * + * @code{.cpp} + * Octree octree(/*max_depth=*/8, /*max_items=*/16); + * octree.build(points); + * auto neighbors = octree.query_knn(query_point, 5); + * @endcode + * + * @see SpatialIndex, BVH, KDTree + */ template class Octree : public SpatialIndex { public: + /** + * @brief 构造八叉树 + * + * @param max_depth 最大递归深度(默认 8,对应 256³ 分辨率) + * @param max_items 节点剖分前可容纳的最大对象数(默认 16) + */ explicit Octree(int max_depth = 8, int max_items = 16); + + /** + * @brief 批量构建八叉树 + * + * 清空已有数据,将所有 items 逐个插入。 + * 构建复杂度 O(n)。 + * + * @param items 待索引的对象数组 + */ void build(const std::vector& items) override; + + /** + * @brief 增量插入对象 + * + * 从根节点递归,到达容纳它的最深层节点。 + * 若节点超出 max_items 且未达 max_depth,自动剖分。 + * + * @param item 待插入的空间对象 + */ void insert(const T& item) override; + + /** + * @brief 删除对象 + * + * @param item 待删除的空间对象 + * @return 删除成功返回 true;对象不存在返回 false + */ bool remove(const T& item) override; + + /** + * @brief 范围查询 + * + * 从根节点递归,只访问与 range 相交的子节点。 + * + * @param range 查询包围盒 + * @return 与 range 相交的对象列表 + */ std::vector query_range(const AABB3D& range) const override; + + /** + * @brief K 近邻查询 + * + * 使用优先队列按距离剪枝遍历八叉树节点。 + * + * @param point 查询点 + * @param k 返回数量 + * @return 最近的 k 个对象 + */ std::vector query_knn(const Point3D& point, size_t k) const override; + + /** + * @brief 射线查询 + * + * 遍历被射线穿过的八叉树节点,收集其中相交对象。 + * + * @param ray 查询射线 + * @return 按命中顺序排列的相交对象列表 + */ std::vector query_ray(const Ray3Dd& ray) const override; + + /** + * @brief 清空八叉树 + */ void clear() override; + + /** + * @brief 当前索引中的对象数量 + * + * @return 对象总数 + */ size_t size() const override; private: + /** + * @brief 递归插入对象到指定节点 + * + * @param node_idx 目标节点索引 + * @param item 待插入的对象 + * @param depth 当前递归深度 + */ void insert_recursive(int node_idx, const T& item, int depth); + + /** + * @brief 递归剖分节点 + * + * 将节点等分为 8 个子立方体并分配现有对象。 + * + * @param node_idx 当前节点索引 + * @param depth 当前深度 + */ void subdivide_recursive(int node_idx, int depth); + + /** + * @brief 递归范围查询 + * + * @param node_idx 当前节点索引 + * @param range 查询包围盒 + * @param result 累积结果 + */ void query_range_recursive(int node_idx, const AABB3D& range, std::vector& result) const; + + /** + * @brief 递归射线查询 + * + * @param node_idx 当前节点索引 + * @param ray 查询射线 + * @param result 累积结果 + */ void query_ray_recursive(int node_idx, const Ray3Dd& ray, std::vector& result) const; - /// Item position helper (for non-Point3D types) + /** + * @brief 获取对象空间位置(用于剖分分配) + * + * @param item 空间对象 + * @return 对象的代表点 + */ static Point3D get_position(const T& item); - std::shared_ptr> data_; - int max_depth_, max_items_; + std::shared_ptr> data_; /**< 八叉树内部数据 */ + int max_depth_; /**< 最大递归深度 */ + int max_items_; /**< 节点剖分阈值 */ }; } // namespace vde::spatial diff --git a/include/vde/spatial/r_tree.h b/include/vde/spatial/r_tree.h index 01e5f11..59955ab 100644 --- a/include/vde/spatial/r_tree.h +++ b/include/vde/spatial/r_tree.h @@ -1,3 +1,13 @@ +/** + * @file r_tree.h + * @brief R 树 (R-Tree) 动态空间索引 + * + * 高度平衡的树形结构,每个节点存储其子节点的最小包围矩形(MBR)。 + * 适合需要频繁插入和删除的动态数据集。 + * + * @ingroup spatial + */ + #pragma once #include "vde/spatial/spatial_index.h" #include "vde/core/aabb.h" @@ -10,37 +20,151 @@ using core::Vector3D; using core::Ray3Dd; using core::Triangle3D; +/** + * @brief R 树节点 + * + * 内部节点存储子节点索引列表;叶节点存储对象索引列表。 + * 每个节点维护其所有子条目的最小包围盒。 + * + * @tparam T 被索引的空间对象类型 + */ template struct RTreeNode { - AABB3D bbox; - bool is_leaf = true; - std::vector children; // internal: indices into nodes_ - std::vector item_indices; // leaf: indices into items_ - size_t parent = static_cast(-1); + AABB3D bbox; /**< 节点包围盒(子节点/对象 MBR 的并集) */ + bool is_leaf = true; /**< 是否为叶节点 */ + std::vector children; /**< 内部节点:子节点索引列表 */ + std::vector item_indices; /**< 叶节点:对象索引列表 */ + size_t parent = static_cast(-1); /**< 父节点索引 */ }; +/** + * @brief R 树动态空间索引 + * + * R 树是一种高度平衡的树,所有叶节点位于同一层。 + * 插入时选择包围盒扩展最小的子节点;溢出时分裂节点。 + * 支持增量插入和删除,适合数据集频繁变化的场景。 + * + * 构建: O(n log n)(逐项插入) + * 范围查询: O(log n + k) + * KNN 查询: O(log n + k log k) + * 插入: O(log n) + * 删除: O(log n) + * + * @tparam T 被索引的空间对象类型 + * + * @note R 树在动态场景(如编辑中的几何体)中比 BVH 更合适。 + * BVH 查询效率更高但构建后不易修改。 + * + * @code{.cpp} + * RTree rtree; + * rtree.build(points); + * rtree.insert(new_point); // 增量插入 + * auto nearby = rtree.query_range(search_box); + * @endcode + * + * @see SpatialIndex, BVH, RStarTree + */ template class RTree : public SpatialIndex { public: + /** + * @brief 批量构建 R 树 + * + * 清空已有数据,将所有 items 逐个插入。 + * 构建复杂度 O(n log n)。 + * + * @param items 待索引的对象数组 + */ void build(const std::vector& items) override; + + /** + * @brief 增量插入对象 + * + * 从根节点出发选择包围盒扩展最小的子节点递归插入。 + * 节点溢出时分裂。复杂度 O(log n)。 + * + * @param item 待插入的空间对象 + */ void insert(const T& item) override; + + /** + * @brief 删除对象 + * + * 定位到所在叶节点后移除。 + * 复杂度 O(log n)。 + * + * @param item 待删除的空间对象 + * @return 删除成功返回 true + */ bool remove(const T& item) override; + + /** + * @brief 范围查询 + * + * 从根节点遍历,仅访问与 range 相交的子节点。 + * 复杂度 O(log n + k)。 + * + * @param range 查询包围盒 + * @return 与 range 相交的对象列表 + */ std::vector query_range(const AABB3D& range) const override; + + /** + * @brief K 近邻查询 + * + * 使用优先队列按 MBR 最小距离剪枝。 + * + * @param point 查询点 + * @param k 返回数量 + * @return 最近的 k 个对象 + */ std::vector query_knn(const Point3D& point, size_t k) const override; + + /** + * @brief 射线查询 + * + * 从根节点递归,遍历被射线穿过的子节点。 + * + * @param ray 查询射线 + * @return 与射线相交的对象列表 + */ std::vector query_ray(const Ray3Dd& ray) const override; + + /** + * @brief 清空 R 树 + */ void clear() override; + + /** + * @brief 对象数量 + * + * @return 当前索引的对象总数 + */ size_t size() const override { return count_; } + + /** + * @brief R 树节点总数 + * + * @return 内部节点 + 叶节点数量 + */ size_t node_count() const { return nodes_.size(); } private: + /** + * @brief 递归范围查询 + * + * @param node_idx 当前节点索引 + * @param range 查询包围盒 + * @param result 累积结果 + */ void query_range_recursive(size_t node_idx, const AABB3D& range, std::vector& result) const; - std::vector items_; - std::vector item_bounds_; - std::vector> nodes_; - size_t root_idx_ = 0; - size_t count_ = 0; + std::vector items_; /**< 对象数组 */ + std::vector item_bounds_; /**< 对象包围盒数组 */ + std::vector> nodes_; /**< R 树节点数组 */ + size_t root_idx_ = 0; /**< 根节点索引 */ + size_t count_ = 0; /**< 对象计数 */ }; } // namespace vde::spatial