|
|
#pragma once
|
|
|
|
|
|
#include <cmath>
|
|
|
#include <algorithm>
|
|
|
#include <vector>
|
|
|
|
|
|
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<double>::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<double>::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<double>::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<double>::epsilon() || len2 < std::numeric_limits<double>::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;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
}
|