docs: doxygen annotations for spatial + boolean + collision

This commit is contained in:
茂之钳
2026-07-24 11:02:06 +00:00
parent 10847e61ad
commit b97b0415f5
11 changed files with 1262 additions and 76 deletions
+47 -3
View File
@@ -1,3 +1,13 @@
/**
* @file boolean_2d.h
* @brief 二维多边形布尔运算
*
* 支持并集、交集、差集、对称差等标准布尔操作。
* 基于 Vatti 算法实现。
*
* @ingroup boolean
*/
#pragma once
#include "vde/core/polygon.h"
#include <vector>
@@ -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<Polygon2D> boolean_2d(const Polygon2D& a, const Polygon2D& b, BooleanOp op);
} // namespace vde::boolean
+158 -14
View File
@@ -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
+42 -2
View File
@@ -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 <vector>
@@ -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<Polygon2D> polygon_offset(const Polygon2D& poly, double distance);
} // namespace vde::boolean
+103 -4
View File
@@ -1,3 +1,13 @@
/**
* @file gjk.h
* @brief GJK (GilbertJohnsonKeerthi) 碰撞检测算法
*
* 检测任意凸形状之间的碰撞与最近距离。
* 通过 Minkowski 差和支撑函数迭代逼近最近点对。
*
* @ingroup collision
*/
#pragma once
#include "vde/core/point.h"
#include <functional>
@@ -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<Point3D(const Vector3D&)>;
/**
* @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
+154 -10
View File
@@ -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 = (1uv)*v0 + u*v1 + v*v2 */
Point3D point; /**< 命中点的世界坐标 */
};
// Ray-Triangle (Möller-Trumbore)
/**
* @brief 射线-三角形求交(Möller-Trumbore 算法)
*
* 标准 MT 算法,直接计算重心坐标 u, v 和参数 t。
* 不预先计算平面方程,一步到位检测。
*
* 算法:
* 1. 计算两个边向量 e1 = v1v0, e2 = v2v0
* 2. 计算 pvec = dir × e2, det = dot(e1, pvec)
* 3. 若 |det| < ε → 射线平行于三角形(无命中)
* 4. 计算 tvec = originv0, 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<RayTriResult> 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<RayTriResult> 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<RaySphereResult> 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<RaySphereResult> 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<double> 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<double> ray_plane_intersect(
const Ray3Dd& ray, const Point3D& plane_point,
const Vector3D& plane_normal) {
@@ -58,11 +174,39 @@ inline std::optional<double> 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<RayTriResult> ray_mesh_intersect(
const Point3D& origin, const Vector3D& dir,
const std::vector<Triangle3D>& triangles);
/**
* @brief 射线-网格求交(Ray3Dd 重载)
*
* @param ray 射线
* @param triangles 三角形列表
* @return 最近命中结果;无命中返回空
*/
inline std::optional<RayTriResult> ray_mesh_intersect(
const Ray3Dd& ray, const std::vector<Triangle3D>& triangles) {
return ray_mesh_intersect(ray.origin(), ray.direction(), triangles);
+47
View File
@@ -1,3 +1,13 @@
/**
* @file sat.h
* @brief SAT (Separating Axis Theorem) 碰撞检测
*
* 基于分离轴定理的凸多面体相交检测。
* 若存在一条轴使得两形状投影不重叠,则不相交。
*
* @ingroup collision
*/
#pragma once
#include "vde/core/point.h"
#include <vector>
@@ -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<Point3D> va = {{0,0,0}, {1,0,0}, {0,1,0}, {0,0,1}};
* std::vector<std::array<int,3>> fa = {{0,2,1}, {0,1,3}, {0,3,2}, {1,2,3}};
* std::vector<Point3D> vb = {{0.5,0.5,0.5}, {1.5,0.5,0.5}, ...};
* std::vector<std::array<int,3>> fb = { ... };
* bool hit = sat_intersect(va, fa, vb, fb);
* @endcode
*
* @see gjk_intersect, tri_tri_intersect
*/
bool sat_intersect(const std::vector<Point3D>& verts_a,
const std::vector<std::array<int,3>>& faces_a,
const std::vector<Point3D>& verts_b,
+100 -5
View File
@@ -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
+186 -14
View File
@@ -1,3 +1,16 @@
/**
* @file bvh.h
* @brief 包围盒层次结构 (Bounding Volume Hierarchy)
*
* 基于二叉树的自适应空间划分结构,适用于三角形网格的快速
* 射线求交、范围查询与最近邻查询。
*
* 默认使用 SAH (Surface Area Heuristic) 启发式策略进行
* 自顶向下构建,在质量和构建时间之间取得较好平衡。
*
* @ingroup spatial
*/
#pragma once
#include <optional>
#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<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(); }
/// 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<HitResult> 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<Node> nodes_;
std::vector<Triangle3D> primitives_;
BVHBuildOptions opts_;
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;
};
+146 -11
View File
@@ -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 <vector>
@@ -9,43 +19,168 @@ using core::Vector3D;
using core::Ray3Dd;
using core::Triangle3D;
/**
* @brief k-d 树空间索引
*
* 在每一层选择一个坐标轴,以该轴上中位数为分割面,
* 将空间划分为两个半空间,递归构建二叉树。
*
* 构建: O(n log n) 使用中位数分区
* 范围查询: O(n^(11/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<Point3D> 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 <typename T>
class KDTree : public SpatialIndex<T> {
public:
/**
* @brief 批量构建 k-d 树
*
* 递归选择分裂轴和分裂点进行自顶向下构建。
* 构建复杂度 O(n log n)。
*
* @param items 待索引的对象数组
*/
void build(const std::vector<T>& 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<T> query_range(const AABB3D& range) const override;
/**
* @brief K 近邻查询
*
* 最优优先搜索:从根节点出发,使用优先队列按距离剪枝。
* 平均 O(log n),最坏 O(n)。
*
* @param point 查询点
* @param k 返回数量
* @return 最近的 k 个对象
*/
std::vector<T> query_knn(const Point3D& point, size_t k) const override;
/**
* @brief 射线查询
*
* @param ray 查询射线
* @return 与射线相交的对象列表
*/
std::vector<T> 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<T> items_;
std::vector<Node> nodes_;
int root_ = -1;
std::vector<T> items_; /**< 对象数组 */
std::vector<Node> 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<T>& result) const;
/**
* @brief 递归射线查询
*
* @param node_idx 当前节点索引
* @param ray 查询射线
* @param result 累积结果
*/
void query_ray_recursive(int node_idx, const Ray3Dd& ray,
std::vector<T>& result) const;
};
+145 -3
View File
@@ -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 <typename T> struct OctreeNode;
template <typename T> 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<Point3D> octree(/*max_depth=*/8, /*max_items=*/16);
* octree.build(points);
* auto neighbors = octree.query_knn(query_point, 5);
* @endcode
*
* @see SpatialIndex, BVH, KDTree
*/
template <typename T>
class Octree : public SpatialIndex<T> {
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<T>& 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<T> query_range(const AABB3D& range) const override;
/**
* @brief K 近邻查询
*
* 使用优先队列按距离剪枝遍历八叉树节点。
*
* @param point 查询点
* @param k 返回数量
* @return 最近的 k 个对象
*/
std::vector<T> query_knn(const Point3D& point, size_t k) const override;
/**
* @brief 射线查询
*
* 遍历被射线穿过的八叉树节点,收集其中相交对象。
*
* @param ray 查询射线
* @return 按命中顺序排列的相交对象列表
*/
std::vector<T> 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<T>& result) const;
/**
* @brief 递归射线查询
*
* @param node_idx 当前节点索引
* @param ray 查询射线
* @param result 累积结果
*/
void query_ray_recursive(int node_idx, const Ray3Dd& ray,
std::vector<T>& result) const;
/// Item position helper (for non-Point3D types)
/**
* @brief 获取对象空间位置(用于剖分分配)
*
* @param item 空间对象
* @return 对象的代表点
*/
static Point3D get_position(const T& item);
std::shared_ptr<OctreeData<T>> data_;
int max_depth_, max_items_;
std::shared_ptr<OctreeData<T>> data_; /**< 八叉树内部数据 */
int max_depth_; /**< 最大递归深度 */
int max_items_; /**< 节点剖分阈值 */
};
} // namespace vde::spatial
+134 -10
View File
@@ -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 <typename T>
struct RTreeNode {
AABB3D bbox;
bool is_leaf = true;
std::vector<size_t> children; // internal: indices into nodes_
std::vector<size_t> item_indices; // leaf: indices into items_
size_t parent = static_cast<size_t>(-1);
AABB3D bbox; /**< 节点包围盒(子节点/对象 MBR 的并集) */
bool is_leaf = true; /**< 是否为叶节点 */
std::vector<size_t> children; /**< 内部节点:子节点索引列表 */
std::vector<size_t> item_indices; /**< 叶节点:对象索引列表 */
size_t parent = static_cast<size_t>(-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<Point3D> rtree;
* rtree.build(points);
* rtree.insert(new_point); // 增量插入
* auto nearby = rtree.query_range(search_box);
* @endcode
*
* @see SpatialIndex, BVH, RStarTree
*/
template <typename T>
class RTree : public SpatialIndex<T> {
public:
/**
* @brief 批量构建 R 树
*
* 清空已有数据,将所有 items 逐个插入。
* 构建复杂度 O(n log n)。
*
* @param items 待索引的对象数组
*/
void build(const std::vector<T>& 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<T> query_range(const AABB3D& range) const override;
/**
* @brief K 近邻查询
*
* 使用优先队列按 MBR 最小距离剪枝。
*
* @param point 查询点
* @param k 返回数量
* @return 最近的 k 个对象
*/
std::vector<T> query_knn(const Point3D& point, size_t k) const override;
/**
* @brief 射线查询
*
* 从根节点递归,遍历被射线穿过的子节点。
*
* @param ray 查询射线
* @return 与射线相交的对象列表
*/
std::vector<T> 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<T>& result) const;
std::vector<T> items_;
std::vector<AABB3D> item_bounds_;
std::vector<RTreeNode<T>> nodes_;
size_t root_idx_ = 0;
size_t count_ = 0;
std::vector<T> items_; /**< 对象数组 */
std::vector<AABB3D> item_bounds_; /**< 对象包围盒数组 */
std::vector<RTreeNode<T>> nodes_; /**< R 树节点数组 */
size_t root_idx_ = 0; /**< 根节点索引 */
size_t count_ = 0; /**< 对象计数 */
};
} // namespace vde::spatial