#pragma once #include #include #include namespace Geometry { struct Point { double x = 0.0; double y = 0.0; // 运算符重载,方便向量运算 Point operator+(const Point& other) const { return { x + other.x, y + other.y }; } Point operator-(const Point& other) const { return { x - other.x, y - other.y }; } Point operator*(double scalar) const { return { x * scalar, y * scalar }; } // 向量点积 double dot(const Point& other) const { return x * other.x + y * other.y; } // 向量模长平方 (避免开方,用于性能优化) double lengthSq() const { return x * x + y * y; } // 向量模长 double length() const { return std::sqrt(lengthSq()); } }; struct Segment { Point start; Point end; // 获取线段的方向向量 (End - Start) Point direction() const { return end - start; } }; // 结果结构体 struct PointSnapResult { Point closestPoint; // 线段上离目标最近的点 double distance; // 实际距离 }; /** * * GeometryUtils 工具类 */ class GeometryUtils { public: /** * 计算点在直线上(由线段定义)的投影比例 t * P_proj = Start + t * (End - Start) */ static double getProjectionFactor(const Point& point, const Segment& lineSeg) { Point ab = lineSeg.direction(); double lenSq = ab.lengthSq(); if (lenSq < std::numeric_limits::epsilon()) { return 0.0; } Point ap = point - lineSeg.start; return ap.dot(ab) / lenSq; } /** * 计算点到线段的最短距离及最近点 * \param point 查询点 * \param segment 目标线段 * \return 包含最近点坐标和距离的结构体 */ static PointSnapResult pointToSegmentDistance(const Point& point, const Segment& segment) { Point ab = segment.direction(); Point ap = point - segment.start; double abLenSq = ab.lengthSq(); // 边界情况:线段长度为0(退化为一个点) if (abLenSq < std::numeric_limits::epsilon()) { double dist = ap.length(); return { segment.start, dist }; } // 计算投影系数 t // t = (AP . AB) / |AB|^2 double t = ap.dot(ab) / abLenSq; // 限制 t 在 [0, 1] 之间(夹具 Clamping) // 如果 t < 0,说明最近点在 start 外侧;如果 t > 1,在 end 外侧 t = max(0.0, min(1.0, t)); // 计算最近点坐标: Closest = Start + t * AB Point closest = segment.start + (ab * t); // 计算距离 double dist = (point - closest).length(); return { closest, dist }; } /** * 计算点到无限长直线的垂直距离平方 */ static double pointToLineDistanceSq(const Point& point, const Segment& lineSeg) { Point ab = lineSeg.direction(); Point ap = point - lineSeg.start; double abLenSq = ab.lengthSq(); if (abLenSq < std::numeric_limits::epsilon()) { return ap.lengthSq(); } // 叉积的模 (2D叉积 = x1*y2 - x2*y1) 表示平行四边形面积 // 面积 = 底 * 高 => 高 = 面积 / 底 double crossProduct = ab.x * ap.y - ab.y * ap.x; return (crossProduct * crossProduct) / abLenSq; } /** * 检查两个共线/平行线段是否在投影方向上重叠 * 用于防止吸附到虽然平行但实际上错开很远的线段 */ static bool areSegmentsOverlapping(const Segment& seg1, const Segment& seg2) { // 将 Seg2 的两个端点投影到 Seg1 的直线上,看 t 值范围 double t1 = getProjectionFactor(seg2.start, seg1); double t2 = getProjectionFactor(seg2.end, seg1); double minT = min(t1, t2); double maxT = max(t1, t2); // Seg1 的 t 范围是 [0, 1] // 检查区间 [minT, maxT] 和 [0, 1] 是否有交集 return max(0.0, minT) <= min(1.0, maxT); } /** * 判断两条线段是否“接近平行” * \param seg1 线段1 * \param seg2 线段2 * \param toleranceDegrees 允许的偏差角度(度数) * \return true 如果接近平行 */ static bool isNearlyParallel(const Segment& seg1, const Segment& seg2, double toleranceDegrees) { Point v1 = seg1.direction(); Point v2 = seg2.direction(); double len1 = v1.length(); double len2 = v2.length(); // 防止零向量 if (len1 < std::numeric_limits::epsilon() || len2 < std::numeric_limits::epsilon()) { return false; } // 计算单位向量的点积 // cos(theta) = (v1 . v2) / (|v1| * |v2|) double cosTheta = v1.dot(v2) / (len1 * len2); // 平行意味着角度接近 0度 (cos=1) 或 180度 (cos=-1) // 所以我们需要检查 abs(cosTheta) 是否接近 1 // 将容差角度转换为弧度 double toleranceRad = toleranceDegrees * (M_PI / 180.0); // 计算阈值。如果容差是 5度,那么 cos(5度) 约为 0.996 // 只要 abs(cosTheta) > cos(tolerance),说明夹角小于 tolerance double threshold = std::cos(toleranceRad); return std::abs(cosTheta) >= threshold; } }; }