docs: doxygen annotations for curves + mesh + sketch
CI / Build & Test (push) Failing after 29s
CI / Release Build (push) Failing after 38s

This commit is contained in:
茂之钳
2026-07-24 11:04:04 +00:00
parent b97b0415f5
commit 4c9ee4f760
53 changed files with 5307 additions and 506 deletions
+81
View File
@@ -5,50 +5,131 @@
namespace vde::core {
/**
* @brief 轴对齐包围盒(AABB
*
* 用最小/最大两个角点表示的三维轴对齐包围盒。
* 支持增量扩展、包含/相交测试、几何属性查询。
*
* @tparam T 标量类型(通常为 double 或 float
*
* @note 初始状态为空(min = +∞, max = -∞),需要通过 expand() 初始化
*
* @ingroup core
*/
template <typename T>
class AABB {
public:
/**
* @brief 构造空包围盒
*
* min 初始化为 T 最大值,max 初始化为 T 最小值,
* 表示"无内容"的空状态。
*/
AABB() : min_(Point3D::Constant(std::numeric_limits<T>::max())),
max_(Point3D::Constant(std::numeric_limits<T>::lowest())) {}
/**
* @brief 从两个角点构造包围盒
*
* @param min 最小角点
* @param max 最大角点
*
* @pre min 的各分量 ≤ max 的各分量
*/
AABB(const Point3D& min, const Point3D& max) : min_(min), max_(max) {}
/**
* @brief 扩展包围盒以包含给定点
*
* 逐分量取 min/max,保证包围盒包含该点。
* 若包围盒为空,此调用等效于初始化为包含该点的退化盒。
*
* @param p 要包含的点
*
* @code{.cpp}
* AABB3D box;
* for (auto& p : points) box.expand(p);
* @endcode
*/
void expand(const Point3D& p) {
min_ = min_.cwiseMin(p);
max_ = max_.cwiseMax(p);
}
/**
* @brief 扩展包围盒以包含另一个 AABB
*
* @param other 另一个包围盒
*/
void expand(const AABB& other) {
min_ = min_.cwiseMin(other.min_);
max_ = max_.cwiseMax(other.max_);
}
/**
* @brief 包围盒中心点
* @return (min + max) / 2
*/
[[nodiscard]] Point3D center() const { return (min_ + max_) * 0.5; }
/**
* @brief 包围盒对角线向量(从 min 到 max)
* @return max - min
*/
[[nodiscard]] Vector3D extent() const { return max_ - min_; }
/**
* @brief 包围盒表面积
* @return 2 * (dx*dy + dy*dz + dz*dx)
*/
[[nodiscard]] T surface_area() const {
Vector3D e = extent();
return 2.0 * (e.x() * e.y() + e.y() * e.z() + e.z() * e.x());
}
/**
* @brief 包围盒体积
* @return dx * dy * dz
*/
[[nodiscard]] T volume() const {
Vector3D e = extent();
return e.x() * e.y() * e.z();
}
/**
* @brief 测试点是否在包围盒内部(含边界)
*
* @param p 测试点
* @return true 若 min ≤ p ≤ max(各分量)
*/
[[nodiscard]] bool contains(const Point3D& p) const {
return (p.array() >= min_.array()).all() && (p.array() <= max_.array()).all();
}
/**
* @brief 测试两个包围盒是否相交(含边界接触)
*
* @param other 另一个包围盒
* @return true 若存在交集
*/
[[nodiscard]] bool intersects(const AABB& other) const {
return (min_.array() <= other.max_.array()).all()
&& (max_.array() >= other.min_.array()).all();
}
/// 获取最小角点
[[nodiscard]] const Point3D& min() const { return min_; }
/// 获取最大角点
[[nodiscard]] const Point3D& max() const { return max_; }
private:
Point3D min_, max_;
};
/// 双精度三维包围盒
using AABB3D = AABB<double>;
/// 单精度三维包围盒
using AABB3f = AABB<float>;
} // namespace vde::core
+45 -3
View File
@@ -4,11 +4,53 @@
namespace vde::core {
/// 2D convex hull — Graham scan, O(n log n)
/**
* @brief 二维凸包计算(Graham Scan 算法)
*
* 使用 Graham 扫描算法计算点集的凸包,时间复杂度 O(n log n)。
* 以极角排序点为预处理,然后用栈维护凸包边界。
*
* @param points 输入点集(至少 3 个不共线点)
* @return 凸包顶点序列(逆时针排列),不含共线的中间点
*
* @note 自动过滤共线点,返回严格的凸包顶点
* @note 若所有点共线,返回两个端点
*
* @code{.cpp}
* std::vector<Point2D> pts = {{0,0}, {1,0}, {1,1}, {0,1}, {0.5,0.5}};
* auto hull = convex_hull_2d(pts);
* // hull = [(0,0), (1,0), (1,1), (0,1)]
* @endcode
*
* @see convex_hull_3d, Polygon2D::convex_hull_2d
* @ingroup core
*/
std::vector<Point2D> convex_hull_2d(const std::vector<Point2D>& points);
/// 3D convex hull — QuickHull
/// Returns triangles of the hull (counter-clockwise when viewed from outside)
/**
* @brief 三维凸包计算(QuickHull 算法)
*
* 使用 QuickHull 算法计算三维点集的凸包。
* 先构造初始四面体,然后递归地将外部点添加到凸包上。
* 平均复杂度 O(n log n),最坏情况 O(n²)。
*
* @param points 输入点集(至少 4 个不共面点)
* @return 凸包三角形列表,每个三角形三顶点按逆时针排列(从外部观察)
*
* @note 返回的三角形法线方向朝外
* @note 共面/退化点被自动丢弃
*
* @code{.cpp}
* std::vector<Point3D> pts = /* 3D 点云 */;
* auto hull_tris = convex_hull_3d(pts);
* for (auto& tri : hull_tris) {
* // tri = {p0, p1, p2} — CCW when viewed from outside
* }
* @endcode
*
* @see convex_hull_2d
* @ingroup core
*/
std::vector<std::array<Point3D, 3>> convex_hull_3d(const std::vector<Point3D>& points);
} // namespace vde::core
+171
View File
@@ -8,23 +8,194 @@
namespace vde::core {
// ── Point distances ──────────────────────────
/**
* @brief 两点之间的欧氏距离
*
* @param a 第一个点
* @param b 第二个点
* @return ||b - a||
*
* @code{.cpp}
* double d = distance(Point3D(0,0,0), Point3D(3,4,0)); // d == 5.0
* @endcode
*
* @ingroup core
*/
double distance(const Point3D& a, const Point3D& b);
/**
* @brief 点到直线的距离
*
* 计算点到无限直线的垂直距离。
*
* @param p 测试点
* @param line 直线
* @return 垂直距离
*
* @ingroup core
*/
double distance(const Point3D& p, const Line3D<double>& line);
/**
* @brief 点到线段的距离
*
* 若投影点落在线段内部,返回垂直距离;否则返回到最近端点的距离。
*
* @param p 测试点
* @param seg 线段
* @return 最短距离
*
* @ingroup core
*/
double distance(const Point3D& p, const Segment3D<double>& seg);
/**
* @brief 点到平面的距离
*
* 返回绝对值(总是非负)。
*
* @param p 测试点
* @param plane 平面
* @return 绝对垂直距离
*
* @see Plane::signed_distance
* @ingroup core
*/
double distance(const Point3D& p, const Plane<double>& plane);
/**
* @brief 点到三角形的距离
*
* 若投影点在三角形内部,返回垂直距离;否则返回到最近的边或顶点的距离。
*
* @param p 测试点
* @param tri 三角形
* @return 最短距离
*
* @ingroup core
*/
double distance(const Point3D& p, const Triangle<double>& tri);
/**
* @brief 点到包围盒的距离
*
* 若点在包围盒内,返回 0;否则返回到最近面的距离。
*
* @param p 测试点
* @param box 包围盒
* @return 最短距离(内部点返回 0)
*
* @ingroup core
*/
double distance(const Point3D& p, const AABB<double>& box);
// ── Feature-pair distances ───────────────────
/**
* @brief 两条线段之间的最短距离
*
* 处理平行、相交、最近端点到端点等多种情况。
*
* @param a 第一条线段
* @param b 第二条线段
* @return 最短距离(若相交则为 0)
*
* @ingroup core
*/
double distance(const Segment3D<double>& a, const Segment3D<double>& b);
/**
* @brief 两条直线之间的最短距离
*
* 若相交则为 0;若平行则返回任意垂线段长度;若异面则返回公垂线长度。
*
* @param a 第一条直线
* @param b 第二条直线
* @return 最短距离
*
* @ingroup core
*/
double distance(const Line3D<double>& a, const Line3D<double>& b);
/**
* @brief 两个包围盒之间的最短距离
*
* 若相交则返回 0。
*
* @param a 第一个包围盒
* @param b 第二个包围盒
* @return 最短距离
*
* @ingroup core
*/
double distance(const AABB<double>& a, const AABB<double>& b);
/**
* @brief 两个三角形之间的最短距离
*
* 处理分离、边相交、面重叠等情况。
*
* @param a 第一个三角形
* @param b 第二个三角形
* @return 最短距离(若相交则为 0)
*
* @ingroup core
*/
double distance(const Triangle<double>& a, const Triangle<double>& b);
// ── Closest-point queries ────────────────────
/**
* @brief 三角形上距给定点最近的点
*
* 使用重心坐标将投影点截断到三角形内部。
* 若投影在三角形外,返回最近边或顶点上的点。
*
* @param p 查询点
* @param tri 三角形
* @return 三角形上距离 p 最近的点
*
* @ingroup core
*/
Point3D closest_point(const Point3D& p, const Triangle<double>& tri);
/**
* @brief 线段上距给定点最近的点
*
* 通过参数 t = clamp(投影, 0, 1) 实现。
*
* @param p 查询点
* @param seg 线段
* @return 线段上距离 p 最近的点(可能为端点)
*
* @ingroup core
*/
Point3D closest_point(const Point3D& p, const Segment3D<double>& seg);
/**
* @brief 包围盒上距给定点最近的点
*
* 对每个坐标分量做 clamp 到 [min, max]。
*
* @param p 查询点
* @param box 包围盒
* @return 包围盒边界上(或内部)距离 p 最近的点
*
* @ingroup core
*/
Point3D closest_point(const Point3D& p, const AABB<double>& box);
/**
* @brief 平面上距给定点最近的点(正交投影)
*
* @param p 查询点
* @param plane 平面
* @return 点 p 在平面上的正交投影
*
* @see Plane::project
* @ingroup core
*/
Point3D closest_point(const Point3D& p, const Plane<double>& plane);
} // namespace vde::core
+52 -8
View File
@@ -9,18 +9,62 @@ using foundation::Point3D;
using foundation::Vector2D;
using foundation::Vector3D;
/**
* @brief ICP(迭代最近点)配准结果
*
* 包含刚体变换、误差度量、迭代信息和收敛状态。
*
* @ingroup core
*/
struct ICPResult {
Transform3D transform; // Rigid transform aligning source to target
double rms_error; // Root-mean-square error
int iterations; // Number of iterations used
/// 将 source 对齐到 target 的刚体变换
Transform3D transform;
/**
* @brief 配准后的均方根误差
*
* RMS = sqrt(Σ ||target_i - T(source_i)||² / N)
*/
double rms_error;
/// 实际使用的迭代次数
int iterations;
/// 是否在达到最大迭代次数前收敛
bool converged;
};
/// Iterative Closest Point (ICP) for rigid point cloud registration
/// @param source Source point cloud (moved)
/// @param target Target point cloud (fixed)
/// @param max_iter Maximum iterations
/// @param tolerance Convergence tolerance on RMS change
/**
* @brief 迭代最近点(ICP)刚体点云配准
*
* 使用标准 ICP 算法将 source 点云配准到 target 点云。
* 每步迭代:
* 1. 对每个 source 点找 target 中的最近邻
* 2. 用 SVD 求解最优刚体变换
* 3. 应用变换,检查收敛
*
* @param source 源点云(将被移动)
* @param target 目标点云(固定参考)
* @param max_iter 最大迭代次数(默认 50)
* @param tolerance 收敛容差:相邻两次 RMS 变化的阈值(默认 1e-6)
* @return 配准结果,包含最终变换和误差
*
* @note source 和 target 点数可以不同
* @note 初始对齐越好,收敛越快;若初始偏差太大,可能收敛到局部最优
*
* @code{.cpp}
* std::vector<Point3D> src = /* ... */;
* std::vector<Point3D> tgt = /* ... */;
* auto result = icp_register(src, tgt, 100, 1e-8);
* if (result.converged) {
* std::cout << "RMS error: " << result.rms_error << "\n";
* // 应用变换
* for (auto& p : src) p = result.transform * p;
* }
* @endcode
*
* @ingroup core
*/
[[nodiscard]] ICPResult icp_register(const std::vector<Point3D>& source,
const std::vector<Point3D>& target,
int max_iter = 50,
+92
View File
@@ -3,49 +3,141 @@
namespace vde::core {
/**
* @brief 三维无限直线
*
* 由原点位置和单位方向向量表示。
* 参数方程:P(t) = origin + direction * t。
*
* @tparam T 标量类型
*
* @ingroup core
*/
template <typename T>
class Line3D {
public:
/// 默认构造:原点在 (0,0,0),方向沿 X 轴
Line3D() = default;
/**
* @brief 从原点和方向构造直线
*
* @param origin 直线上一点
* @param direction 方向向量(自动归一化)
*/
Line3D(const Point3D& origin, const Vector3D& direction)
: origin_(origin), direction_(direction.normalized()) {}
/// 获取直线上的一点(原点)
[[nodiscard]] const Point3D& origin() const { return origin_; }
/// 获取单位方向向量
[[nodiscard]] const Vector3D& direction() const { return direction_; }
/**
* @brief 参数方程求点
*
* @param t 参数值
* @return origin + direction * t
*/
[[nodiscard]] Point3D point_at(T t) const { return origin_ + direction_ * t; }
private:
Point3D origin_{0,0,0};
Vector3D direction_{1,0,0};
};
/**
* @brief 三维线段
*
* 由两个端点定义的有界直线段。
*
* @tparam T 标量类型
*
* @ingroup core
*/
template <typename T>
class Segment3D {
public:
/// 默认构造:两个端点均在原点
Segment3D() = default;
/**
* @brief 从两个端点构造线段
*
* @param p0 起点
* @param p1 终点
*/
Segment3D(const Point3D& p0, const Point3D& p1) : p0_(p0), p1_(p1) {}
/// 获取起点
[[nodiscard]] const Point3D& p0() const { return p0_; }
/// 获取终点
[[nodiscard]] const Point3D& p1() const { return p1_; }
/**
* @brief 方向向量(未归一化)
* @return p1 - p0
*/
[[nodiscard]] Vector3D direction() const { return p1_ - p0_; }
/**
* @brief 线段长度
* @return ||p1 - p0||
*/
[[nodiscard]] T length() const { return direction().norm(); }
private:
Point3D p0_{0,0,0}, p1_{0,0,0};
};
/**
* @brief 三维射线
*
* 由原点和方向定义的半无限直线(t ≥ 0)。
* 参数方程:P(t) = origin + direction * t, t ≥ 0。
*
* @tparam T 标量类型
*
* @ingroup core
*/
template <typename T>
class Ray3D {
public:
/// 默认构造:原点在 (0,0,0),方向沿 X 轴
Ray3D() = default;
/**
* @brief 从原点和方向构造射线
*
* @param origin 射线起点
* @param direction 方向向量(自动归一化)
*/
Ray3D(const Point3D& origin, const Vector3D& direction)
: origin_(origin), direction_(direction.normalized()) {}
/// 获取射线起点
[[nodiscard]] const Point3D& origin() const { return origin_; }
/// 获取单位方向向量
[[nodiscard]] const Vector3D& direction() const { return direction_; }
/**
* @brief 参数方程求点(t ≥ 0)
*
* @param t 参数值(应为非负)
* @return origin + direction * t
*/
[[nodiscard]] Point3D point_at(T t) const { return origin_ + direction_ * t; }
private:
Point3D origin_{0,0,0};
Vector3D direction_{1,0,0};
};
/// 双精度三维直线
using Line3Dd = Line3D<double>;
/// 双精度三维线段
using Segment3Dd = Segment3D<double>;
/// 双精度三维射线
using Ray3Dd = Ray3D<double>;
} // namespace vde::core
+66
View File
@@ -3,19 +3,84 @@
namespace vde::core {
/**
* @brief 三维平面
*
* 用 Hessian 法式表示:normal · p + d = 0。
* 其中 normal 为单位法向量,d 为原点到平面的有向距离的负值。
*
* @tparam T 标量类型(通常为 double
*
* @ingroup core
*/
template <typename T>
class Plane {
public:
/// 默认构造:法线 (0,0,1)d=0XY 平面)
Plane() = default;
/**
* @brief 从点和法线构造平面
*
* @param point 平面上一点
* @param normal 法向量(自动归一化)
*/
Plane(const Point3D& point, const Vector3D& normal)
: normal_(normal.normalized()), d_(-normal_.dot(point)) {}
/**
* @brief 从三点构造平面
*
* @param a,b,c 平面上不共线的三点(右手定则决定法线方向)
*
* @pre 三点不共线
*/
Plane(const Point3D& a, const Point3D& b, const Point3D& c)
: Plane(a, (b - a).cross(c - a)) {}
/// 获取单位法向量
[[nodiscard]] const Vector3D& normal() const { return normal_; }
/**
* @brief 获取平面方程常数 d
*
* 平面方程为 normal · x + d = 0。
* d 是原点到平面有向距离的负值。
*
* @return d 值
*/
[[nodiscard]] T d() const { return d_; }
/**
* @brief 有向点到平面距离
*
* 正表示点在法线方向一侧,负表示相反侧。
*
* @param p 测试点
* @return normal · p + d
*
* @code{.cpp}
* Plane<double> plane(Point3D(0,0,0), Vector3D::UnitZ());
* double sd = plane.signed_distance(Point3D(1,2,3));
* // sd == 3.0 (点在法线方向,正)
* @endcode
*/
[[nodiscard]] T signed_distance(const Point3D& p) const { return normal_.dot(p) + d_; }
/**
* @brief 绝对点到平面距离
*
* @param p 测试点
* @return |signed_distance(p)|
*/
[[nodiscard]] T distance(const Point3D& p) const { return std::abs(signed_distance(p)); }
/**
* @brief 点在平面上的正交投影
*
* @param p 原点
* @return 投影点(满足在平面上且连线垂直于平面)
*/
[[nodiscard]] Point3D project(const Point3D& p) const {
return p - normal_ * signed_distance(p);
}
@@ -25,6 +90,7 @@ private:
T d_{0};
};
/// 双精度三维平面
using Plane3D = Plane<double>;
} // namespace vde::core
+32
View File
@@ -2,20 +2,52 @@
#include "vde/foundation/math_types.h"
#include "vde/foundation/tolerance.h"
/**
* @defgroup core 核心几何模块
*
* 核心模块提供 VDE 引擎的几何操作 API:
* - 基础几何体(AABB、直线、射线、线段、平面、三角形、多边形)
* - 几何计算(距离、最近点、凸包、变换)
* - 点云配准(ICP
* - Voronoi 图
*
* @{
*/
namespace vde::core {
/**
* @brief 通用 N 维点类型(重新导出,便于 core 模块使用)
*
* @tparam T 标量类型
* @tparam Dim 维度
*/
template <typename T, size_t Dim>
using Point = foundation::Point<T, Dim>;
/// 双精度二维点(重新导出)
using Point2D = foundation::Point2D;
/// 双精度三维点(重新导出)
using Point3D = foundation::Point3D;
/// 单精度二维点(重新导出)
using Point2f = foundation::Point2f;
/// 单精度三维点(重新导出)
using Point3f = foundation::Point3f;
/**
* @brief 通用 N 维向量类型(重新导出)
*
* @tparam T 标量类型
* @tparam Dim 维度
*/
template <typename T, size_t Dim>
using Vector = foundation::Vector<T, Dim>;
/// 双精度二维向量(重新导出)
using Vector2D = foundation::Vector2D;
/// 双精度三维向量(重新导出)
using Vector3D = foundation::Vector3D;
} // namespace vde::core
/** @} */ // end of core group
+120 -11
View File
@@ -6,44 +6,153 @@
namespace vde::core {
/**
* @brief 二维平面多边形
*
* 支持面积计算、点包含测试、简化和三角剖分。
* 顶点按顺序排列(闭合多边形),不存储首尾重复顶点。
*
* @note 内部假设简单多边形(无自交),非简单多边形的行为未定义
* @note 所有几何计算基于(假设的)XY 平面
*
* @ingroup core
*/
class Polygon2D {
public:
/// 构造空多边形
Polygon2D() = default;
/**
* @brief 从顶点列表构造多边形
*
* @param vertices 顶点序列(逆时针或顺时针均可)
*/
explicit Polygon2D(std::vector<Point2D> vertices) : vertices_(std::move(vertices)) {}
/// 获取顶点列表
[[nodiscard]] const std::vector<Point2D>& vertices() const { return vertices_; }
/// 获取顶点数量
[[nodiscard]] size_t size() const { return vertices_.size(); }
/// 多边形是否为空(< 3 个顶点视为退化)
[[nodiscard]] bool empty() const { return vertices_.empty(); }
/// Signed area (positive = CCW)
/**
* @brief 有向面积(Shoelace 公式)
*
* 正值表示逆时针(CCW),负值表示顺时针(CW),零表示自交或退化。
*
* @return 有向面积
*
* @code{.cpp}
* Polygon2D poly({{0,0}, {1,0}, {0,1}});
* double sa = poly.signed_area(); // > 0 (CCW)
* @endcode
*/
[[nodiscard]] double signed_area() const;
/// Absolute area
/**
* @brief 绝对面积
* @return |signed_area()|
*/
[[nodiscard]] double area() const { return std::abs(signed_area()); }
/// Perimeter (sum of edge lengths)
/**
* @brief 周长(各边长之和)
* @return 总边长
*/
[[nodiscard]] double perimeter() const;
/// Area-weighted centroid
/**
* @brief 面积加权质心
*
* 使用多边形三角剖分的面积加权平均计算质心。
* 适用于非自交的任意多边形。
*
* @return 质心坐标
*/
[[nodiscard]] Point2D centroid() const;
/// Axis-aligned bounding box (2D, stored in AABB3D with z=0)
/**
* @brief 二维轴对齐包围盒
*
* 返回 AABBz=0),min/max 的 z 分量均为 0。
*
* @return 包围盒
*/
[[nodiscard]] AABB<double> bounding_box() const;
/// Point-in-polygon test (ray casting)
/**
* @brief 点包含测试(射线法)
*
* 从测试点向右发射水平射线,计算与多边形边的交点数量。
* 奇数次交点在内部,偶数次在外部。
*
* @param p 测试点
* @return true 若点在多边形内部或边界上
*
* @note 边界上的点也返回 true
*/
[[nodiscard]] bool contains(const Point2D& p) const;
/// Is the polygon counter-clockwise?
/**
* @brief 多边形是否为逆时针方向
* @return signed_area() > 0
*/
[[nodiscard]] bool is_ccw() const { return signed_area() > 0; }
/// Douglas-Peucker simplification. Returns a new simplified polygon.
/**
* @brief Douglas-Peucker 简化
*
* 用递归的 Douglas-Peucker 算法减少顶点数量,
* 保证简化后多边形与原多边形的最大偏差不超过 tolerance。
*
* @param tolerance 允许的最大偏差
* @return 简化后的新多边形
*
* @code{.cpp}
* // 将 1000 个顶点的多边形简化为约 100 个
* auto simple = poly.simplify(0.01);
* @endcode
*
* @see triangulate
*/
[[nodiscard]] Polygon2D simplify(double tolerance) const;
/// Ear-clipping triangulation. Returns triangles as triples of vertex indices.
/// Assumes a simple polygon (no self-intersections). CCW ordering required.
/**
* @brief 耳切法三角剖分(Ear Clipping
*
* 将简单多边形剖分为三角形,返回每个三角形的顶点索引三元组。
*
* @return 三角形列表,每个元素为 {i, j, k} 表示顶点索引
*
* @pre 多边形为简单多边形(无自交)
* @pre 顶点为逆时针顺序(CCW)
*
* @note 时间复杂度 O(n²),适用于顶点数 < 1000 的多边形
*
* @code{.cpp}
* Polygon2D poly(/* CCW vertices */);
* auto tris = poly.triangulate();
* for (auto& t : tris) {
* // 顶点: poly.vertices()[t[0]], t[1], t[2]
* }
* @endcode
*
* @see simplify
*/
[[nodiscard]] std::vector<std::array<int, 3>> triangulate() const;
/// Andrew's monotone chain convex hull of a point set.
/**
* @brief Andrew's Monotone Chain 二维凸包
*
* 计算点集的凸包,返回逆时针排列的凸包多边形。
* 时间复杂度 O(n log n)。
*
* @param points 输入点集
* @return 凸包多边形(CCW,不含共线中间点)
*
* @see convex_hull_2d
*/
static Polygon2D convex_hull_2d(const std::vector<Point2D>& points);
private:
+96
View File
@@ -4,23 +4,119 @@
namespace vde::core {
/**
* @brief 三维仿射变换类型
*
* 基于 Eigen::Transform,支持平移、旋转、缩放和它们的组合。
* 使用 4×4 齐次矩阵表示。
*
* @ingroup core
*/
using Transform3D = Eigen::Transform<double, 3, Eigen::Affine>;
/**
* @brief 创建平移变换
*
* @param v 平移向量
* @return 平移变换矩阵
*
* @code{.cpp}
* auto T = translate(Vector3D(1, 2, 3));
* Point3D p2 = T * Point3D(0,0,0); // p2 == (1,2,3)
* @endcode
*
* @ingroup core
*/
inline Transform3D translate(const Vector3D& v) {
return Transform3D(Eigen::Translation<double, 3>(v));
}
/**
* @brief 创建平移变换(分量形式)
*
* @param x X 方向位移
* @param y Y 方向位移
* @param z Z 方向位移
* @return 平移变换矩阵
*
* @ingroup core
*/
inline Transform3D translate(double x, double y, double z) {
return translate(Vector3D(x, y, z));
}
/**
* @brief 创建旋转变换(轴-角表示)
*
* @param axis 旋转轴(自动归一化)
* @param angle_rad 旋转角度(弧度)
* @return 旋转变换矩阵
*
* @code{.cpp}
* // 绕 Z 轴旋转 90°
* auto R = rotate(Vector3D::UnitZ(), M_PI / 2);
* @endcode
*
* @ingroup core
*/
inline Transform3D rotate(const Vector3D& axis, double angle_rad) {
return Transform3D(Eigen::AngleAxis<double>(angle_rad, axis.normalized()));
}
/**
* @brief 绕 X 轴旋转
*
* @param rad 旋转角度(弧度)
* @return 旋转变换矩阵
*
* @ingroup core
*/
inline Transform3D rotate_x(double rad) { return rotate(Vector3D::UnitX(), rad); }
/**
* @brief 绕 Y 轴旋转
*
* @param rad 旋转角度(弧度)
* @return 旋转变换矩阵
*
* @ingroup core
*/
inline Transform3D rotate_y(double rad) { return rotate(Vector3D::UnitY(), rad); }
/**
* @brief 绕 Z 轴旋转
*
* @param rad 旋转角度(弧度)
* @return 旋转变换矩阵
*
* @ingroup core
*/
inline Transform3D rotate_z(double rad) { return rotate(Vector3D::UnitZ(), rad); }
/**
* @brief 创建非均匀缩放变换
*
* @param sx X 方向缩放因子
* @param sy Y 方向缩放因子
* @param sz Z 方向缩放因子
* @return 缩放变换矩阵
*
* @note 非均匀缩放可能导致法线方向不准确;使用场景中应考虑法线变换 = (M^{-1})^T
*
* @ingroup core
*/
inline Transform3D scale(double sx, double sy, double sz) {
return Transform3D(Eigen::Scaling(sx, sy, sz));
}
/**
* @brief 创建均匀缩放变换
*
* @param s 各方向统一的缩放因子
* @return 缩放变换矩阵
*
* @ingroup core
*/
inline Transform3D scale(double s) { return scale(s, s, s); }
} // namespace vde::core
+67
View File
@@ -4,24 +4,89 @@
namespace vde::core {
/**
* @brief 三维三角形
*
* 由三个顶点定义的平面三角形面片。
* 支持面积、法线、质心、重心坐标包含测试。
*
* @tparam T 标量类型(通常为 double
*
* @ingroup core
*/
template <typename T>
class Triangle {
public:
/// 默认构造:三个顶点均在原点(退化三角形)
Triangle() = default;
/**
* @brief 从三个顶点构造三角形
*
* @param v0 第一个顶点
* @param v1 第二个顶点
* @param v2 第三个顶点
*/
Triangle(const Point3D& v0, const Point3D& v1, const Point3D& v2)
: v_{v0, v1, v2} {}
/**
* @brief 获取第 i 个顶点
*
* @param i 顶点索引(0, 1, 2
* @return 顶点引用
*/
[[nodiscard]] const Point3D& v(int i) const { return v_[i]; }
/// 获取所有顶点数组
[[nodiscard]] const std::array<Point3D, 3>& vertices() const { return v_; }
/**
* @brief 三角形单位法向量
*
* 使用 (v1 - v0) × (v2 - v0) 的归一化结果。
* 依右手定则方向。
*
* @return 单位法向量
*
* @note 若三角形退化(面积为零),返回零向量
*/
[[nodiscard]] Vector3D normal() const {
return (v_[1] - v_[0]).cross(v_[2] - v_[0]).normalized();
}
/**
* @brief 三角形面积
* @return 0.5 * ||(v1 - v0) × (v2 - v0)||
*/
[[nodiscard]] T area() const {
return (v_[1] - v_[0]).cross(v_[2] - v_[0]).norm() * 0.5;
}
/**
* @brief 三角形质心(几何中心)
* @return (v0 + v1 + v2) / 3
*/
[[nodiscard]] Point3D centroid() const {
return (v_[0] + v_[1] + v_[2]) / 3.0;
}
/**
* @brief 重心坐标包含测试
*
* 将点 p 正交投影到三角形平面上,计算重心坐标并判断是否在三角形内。
*
* @param p 测试点(三维空间中的任意点)
* @return true 若投影点在三角形内部或边界上
*
* @note 该测试等效于三维点在三角形上的包含测试(先投影再测试)
*
* @code{.cpp}
* Triangle3D tri({0,0,0}, {1,0,0}, {0,1,0});
* bool in1 = tri.contains(Point3D(0.2, 0.2, 0)); // true
* bool in2 = tri.contains(Point3D(0.2, 0.2, 5)); // true (投影后判断)
* @endcode
*/
[[nodiscard]] bool contains(const Point3D& p) const {
Vector3D v0 = v_[2] - v_[0], v1 = v_[1] - v_[0], v2 = p - v_[0];
T d00 = v0.dot(v0), d01 = v0.dot(v1), d11 = v1.dot(v1);
@@ -36,7 +101,9 @@ private:
std::array<Point3D, 3> v_{};
};
/// 双精度三维三角形
using Triangle3D = Triangle<double>;
/// 单精度三维三角形
using Triangle3f = Triangle<float>;
} // namespace vde::core
+47 -5
View File
@@ -9,14 +9,56 @@ using foundation::Point3D;
using foundation::Vector2D;
using foundation::Vector3D;
/**
* @brief Voronoi 单元格
*
* 表示二维 Voronoi 图中的一个单元格,
* 包含生成点 site 以及该单元格的边界顶点和边。
*
* @ingroup core
*/
struct VoronoiCell {
Point2D site; // Generator point
std::vector<Point2D> vertices; // Cell boundary vertices
std::vector<std::array<int, 2>> edges; // Edge indices into vertices
/// 生成点(站点)
Point2D site;
/**
* @brief 单元格边界顶点(按逆时针顺序排列)
*
* 对于无界单元格,顶点列表可能不闭合。
*/
std::vector<Point2D> vertices;
/**
* @brief 单元格边界边
*
* 每条边表示为 vertices 中的两个索引。
*/
std::vector<std::array<int, 2>> edges;
};
/// 2D Voronoi diagram from Delaunay dual graph
/// Returns cells for each input point
/**
* @brief 二维 Voronoi 图生成(通过 Delaunay 对偶图)
*
* 从输入点集的 Delaunay 三角剖分构造其对偶 Voronoi 图。
* 每个输入点对应一个 VoronoiCell。
*
* @param points 输入点集(站点)
* @return 每个站点对应的 VoronoiCell 列表(顺序与输入一致)
*
* @note 对于凸包边界上的站点,对应的 Voronoi 单元格无界,顶点不完全闭合
* @note 内部使用 Delaunay 三角剖分作为中间步骤
*
* @code{.cpp}
* std::vector<Point2D> sites = {{0,0}, {1,0}, {0,1}, {1,1}};
* auto cells = voronoi_2d(sites);
* for (auto& cell : cells) {
* std::cout << "Site: " << cell.site.transpose() << "\n";
* std::cout << " Vertices: " << cell.vertices.size() << "\n";
* }
* @endcode
*
* @ingroup core
*/
std::vector<VoronoiCell> voronoi_2d(const std::vector<Point2D>& points);
} // namespace vde::core