S01E10:直线与直线距离最近点坐标

625 阅读2分钟

说明

前面我们已经求得了两条直线间的距离,但有时我们还需要具体的坐标,也就是两条直线上,距离最近的点的坐标。

几何

首先,我们有两条直线:AB 和 CD 先用叉乘,求出垂直于它们的方向向量,并将点 C 沿该向量移动,移动距离等于两直线间的距离, 得到点 C': 注意此时,ABC' 所在的平面,与 CD 平行,与 CC'垂直。而我们要求的点 F,就是将 C'点,沿向量 CD 方向移动一段即可:

这样一来,原来的三维问题,就转变成了平面内的二维问题。我们可以用未知数 x 来表示,即C'F = x * CD。那么如何求得这个未知数 x 呢?我们利用点 C' 在直线 AB 上的投影E(垂足)来求: 因为 C'E 垂直于直线 AB,那么 C'F 在 C'E 上的投影等于 C'E,即 C'F * C'E = ||C'E||^2,也就是 C'F 点乘 C'E 等于 C'E 的长度平方。将C'F = x * CD代入,x * CD * C'E = ||C'E||^2,就可以得到 x 的值:x = ||C'E||^2 / (CD * C'E)

同理,将 C 点沿 CD 方向移动 x 倍,就得到了 CD 上的最近点 G,这样即使 x 有误差也可以保证 G 点的精确度,即一定在在直线 CD 上。要求 F 点可以将 G 点向上移动得到,也可以将 C'点沿向量 CD 移动,缺点是由于 x 有误差,最后得到的点 F 不一定在直线 AB上,可能会有一定偏差,优点是这样得到向量 GF 与直线 AB、CD 的垂直度更高(更接近垂直状态)。 在代码中,还要考虑的是:

  • 当两条直线平行时,距离全都相等,没有距离最近点;
  • 当点 C'与直线 AB 距离过近时,三角形 C'EF 几乎成为一个点,后续计算误差太大,只能近似认为 C'点就在直线 AB 上;

代码

static func footPoints(line1:Line, line2:Line) -> (simd_float3, simd_float3)? {
    let crossValue = cross(line2.direction, line1.direction)
    let vector = line1.position - line2.position
    if length_squared(crossValue) < 0.0001 {
        // 平行
        return nil
    }
    let distanceVector = normalize(crossValue)
    
    let dis = dot(distanceVector, vector)
    
    let point2OnPlane = line2.position + dis * distanceVector
    
    let projectionOnLine1 = projectionOnLine(from: point2OnPlane, to: line1)
    let projectionVector = projectionOnLine1 - point2OnPlane
    
    let squared = length_squared(projectionVector)
    
    if squared < 0.0001 {
        // 垂足是 line2.position
        return (point2OnPlane,line2.position)
    }
    let x1:Float = squared / dot(line2.direction, projectionVector)
    let footPoint2 = point2OnPlane + x1 * line2.direction
    let footPoint1 = footPoint2 - dis * distanceVector
    
    return (footPoint1, footPoint2)
}