二维圆弧与非零有限线段最短距离及对应点求解
二维平面圆弧与有限线段最短距离及对应点计算方案
参数定义
先明确两类几何元素的标准化参数,后续计算统一基于该约定:
- 圆弧:圆心
C(cx, cy)、半径r、起始角θ_start、终止角θ_end(逆时针为角度正方向),圆弧上任意点可表示为P_arc(θ) = (cx + r·cosθ, cy + r·sinθ),合法角度范围为θ ∈ [θ_start, θ_end] - 有限线段:两个端点
A(ax, ay)、B(bx, by),线段上任意点可表示为P_seg(t) = A + t·(B-A),合法参数范围为t ∈ [0, 1]
核心逻辑
两个有界几何元素的最短距离只会出现在有限个候选点对中,计算所有合法候选的距离后取最小值即可,不需要做全局遍历,三类有效候选如下:
- 圆弧与线段的法线投影点对:即圆心到线段的垂线与圆、线段的交点,仅当两个点分别落在圆弧段、线段段范围内时有效
- 线段端点到圆弧的最短距离点对:即线段两个端点A、B分别对应圆弧上的最近点,天然满足点在线段上,仅需校验圆弧上点的范围
- 圆弧端点到线段的最短距离点对:即圆弧两个端点(θ=θ_start、θ=θ_end对应点)分别对应线段上的最近点,天然满足点在圆弧上,仅需校验线段上点的范围
注意:不要遗漏边界端点候选,绝大多数实现出错都是因为仅计算投影点,未考虑投影点落在弧段/线段外的情况——这类场景下最短距离必然出现在元素端点上。
伪代码实现
通用工具函数
const PI = 3.141592653589793 const EPS = 1e-8 // 计算两点欧氏距离 func dist(p1, p2): return sqrt( (p1.x - p2.x)^2 + (p1.y - p2.y)^2 ) // 计算点p到有限线段ab的最近点(返回线段上的点) func closest_point_on_seg(p, a, b): ab_x = b.x - a.x ab_y = b.y - a.y ap_x = p.x - a.x ap_y = p.y - a.y t = (ap_x*ab_x + ap_y*ab_y) / (ab_x*ab_x + ab_y*ab_y) t = max(0, min(1, t)) // 将参数t限制在线段合法范围[0,1] return Point(a.x + t*ab_x, a.y + t*ab_y) // 角度归一化:将theta调整到以ref为起点的[ref, ref+2π)区间 func normalize_angle(theta, ref): while theta < ref: theta += 2*PI while theta >= ref + 2*PI: theta -= 2*PI return theta // 计算点p到指定圆弧的最近点(返回圆弧上的点) func closest_point_on_arc(p, c, r, theta_s, theta_e): dx = p.x - c.x dy = p.y - c.y theta_p = atan2(dy, dx) theta_p = normalize_angle(theta_p, theta_s) // 若点相对圆心的角落在圆弧范围内,最近点为连线与圆的交点 if theta_p >= theta_s - EPS and theta_p <= theta_e + EPS: return Point(c.x + r*cos(theta_p), c.y + r*sin(theta_p)) // 否则最近点为两个圆弧端点中距离更近的那个 p_s = Point(c.x + r*cos(theta_s), c.y + r*sin(theta_s)) p_e = Point(c.x + r*cos(theta_e), c.y + r*sin(theta_e)) return p_s if dist(p, p_s) < dist(p, p_e) else p_e
主计算函数
// 主函数:返回最短距离、圆弧上的最近点、线段上的最近点 func arc_seg_min_dist(arc, seg): // 解包参数 c = arc.center, r = arc.r, theta_s = arc.theta_start, theta_e = arc.theta_end a = seg.point_a, b = seg.point_b candidates = [] // 1. 加入法线投影候选点对 p_seg_proj = closest_point_on_seg(c, a, b) dir_x = p_seg_proj.x - c.x dir_y = p_seg_proj.y - c.y dir_len = sqrt(dir_x*dir_x + dir_y*dir_y) if dir_len > EPS: // 排除线段过圆心导致方向向量为0的特殊情况 p_arc_proj = Point(c.x + dir_x/dir_len*r, c.y + dir_y/dir_len*r) theta_proj = atan2(p_arc_proj.y - c.y, p_arc_proj.x - c.x) theta_proj = normalize_angle(theta_proj, theta_s) if theta_proj >= theta_s - EPS and theta_proj <= theta_e + EPS: candidates.append( (p_arc_proj, p_seg_proj) ) // 2. 加入线段两个端点对应圆弧的最近点对 p_arc_a = closest_point_on_arc(a, c, r, theta_s, theta_e) candidates.append( (p_arc_a, a) ) p_arc_b = closest_point_on_arc(b, c, r, theta_s, theta_e) candidates.append( (p_arc_b, b) ) // 3. 加入圆弧两个端点对应线段的最近点对 p_arc_s = Point(c.x + r*cos(theta_s), c.y + r*sin(theta_s)) p_seg_s = closest_point_on_seg(p_arc_s, a, b) candidates.append( (p_arc_s, p_seg_s) ) p_arc_e = Point(c.x + r*cos(theta_e), c.y + r*sin(theta_e)) p_seg_e = closest_point_on_seg(p_arc_e, a, b) candidates.append( (p_arc_e, p_seg_e) ) // 遍历所有候选找最小值 min_d = INF res_arc_p = null res_seg_p = null for (pa, ps) in candidates: d = dist(pa, ps) if d < min_d: min_d = d res_arc_p = pa res_seg_p = ps return (min_d, res_arc_p, res_seg_p)
实现注意事项
- 所有浮点数范围判断都要加小容差(上述代码用1e-8),不要直接做相等判断,避免浮点精度误差导致合法候选被过滤
- 如果计算的是跨度大于π的优弧,只需调整角度归一化后的区间判断逻辑即可,整体框架不需要改动
- 线段与圆弧相交、相切的场景会被上述逻辑自动覆盖:相交时候选对距离为0,相切时切点会被投影候选捕获
内容的提问来源于stack exchange,提问作者Anon
相关产品推荐
相关产品推荐

