docs: doxygen annotations for curves + mesh + sketch
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=0(XY 平面)
|
||||
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
|
||||
|
||||
@@ -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
@@ -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 二维轴对齐包围盒
|
||||
*
|
||||
* 返回 AABB(z=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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user